From 7ca80410d56d519d1353aae107bee715962ba46f Mon Sep 17 00:00:00 2001 From: Eldon Koyle Date: Tue, 11 Oct 2016 15:29:06 -0600 Subject: [PATCH] feature: Add q-bridge-mib tagged VLAN membership #3285 --- html/includes/print-vlan.inc.php | 16 +++++-- html/pages/device/port/vlans.inc.php | 15 ++++-- includes/discovery/vlans.inc.php | 21 ++++++--- includes/discovery/vlans/cisco-vtp.inc.php | 8 ++-- includes/discovery/vlans/q-bridge-mib.inc.php | 33 +++++++++---- includes/functions.php | 38 +++++++++++++++ sql-schema/144.sql | 1 + tests/VlanFunctionsTest.php | 47 +++++++++++++++++++ 8 files changed, 152 insertions(+), 27 deletions(-) create mode 100644 sql-schema/144.sql create mode 100644 tests/VlanFunctionsTest.php diff --git a/html/includes/print-vlan.inc.php b/html/includes/print-vlan.inc.php index 32f5459ad2..7729bc34b0 100644 --- a/html/includes/print-vlan.inc.php +++ b/html/includes/print-vlan.inc.php @@ -12,15 +12,21 @@ echo ' Vlan '.$vlan['vlan_vlan'].''; echo ''.$vlan['vlan_name'].''; echo ''; - $vlan_ports = array(); - $otherports = dbFetchRows('SELECT * FROM `ports_vlans` AS V, `ports` as P WHERE V.`device_id` = ? AND V.`vlan` = ? AND P.port_id = V.port_id', array($device['device_id'], $vlan['vlan_vlan'])); +$vlan_ports = array(); +$traverse_ifvlan = true; +$otherports = dbFetchRows('SELECT * FROM `ports_vlans` AS V, `ports` as P WHERE V.`device_id` = ? AND V.`vlan` = ? AND P.port_id = V.port_id', array($device['device_id'], $vlan['vlan_vlan'])); foreach ($otherports as $otherport) { + if ($otherport['untagged']) { + $traverse_ifvlan = false; + } $vlan_ports[$otherport[ifIndex]] = $otherport; } - $otherports = dbFetchRows('SELECT * FROM ports WHERE `device_id` = ? AND `ifVlan` = ?', array($device['device_id'], $vlan['vlan_vlan'])); -foreach ($otherports as $otherport) { - $vlan_ports[$otherport[ifIndex]] = array_merge($otherport, array('untagged' => '1')); +if ($traverse_ifvlan) { + $otherports = dbFetchRows('SELECT * FROM ports WHERE `device_id` = ? AND `ifVlan` = ?', array($device['device_id'], $vlan['vlan_vlan'])); + foreach ($otherports as $otherport) { + $vlan_ports[$otherport[ifIndex]] = array_merge($otherport, array('untagged' => '1')); + } } ksort($vlan_ports); diff --git a/html/pages/device/port/vlans.inc.php b/html/pages/device/port/vlans.inc.php index 552892d117..db07de5f0d 100644 --- a/html/pages/device/port/vlans.inc.php +++ b/html/pages/device/port/vlans.inc.php @@ -30,15 +30,24 @@ foreach ($vlans as $vlan) { echo ''.$vlan['cost'].''.$vlan['priority']."".$vlan['state'].''; + $traverse_ifvlan = true; $vlan_ports = array(); $otherports = dbFetchRows('SELECT * FROM `ports_vlans` AS V, `ports` as P WHERE V.`device_id` = ? AND V.`vlan` = ? AND P.port_id = V.port_id', array($device['device_id'], $vlan['vlan'])); foreach ($otherports as $otherport) { + if ($otherport['untagged']) { + $traverse_ifvlan = false; + } $vlan_ports[$otherport[ifIndex]] = $otherport; } - $otherports = dbFetchRows('SELECT * FROM ports WHERE `device_id` = ? AND `ifVlan` = ?', array($device['device_id'], $vlan['vlan'])); - foreach ($otherports as $otherport) { - $vlan_ports[$otherport[ifIndex]] = array_merge($otherport, array('untagged' => '1')); + if ($traverse_ifvlan) { + $otherports = dbFetchRows( + 'SELECT * FROM ports WHERE `device_id` = ? AND `ifVlan` = ?', + array($device['device_id'], $vlan['vlan']) + ); + foreach ($otherports as $otherport) { + $vlan_ports[$otherport[ifIndex]] = array_merge($otherport, array('untagged' => '1')); + } } ksort($vlan_ports); diff --git a/includes/discovery/vlans.inc.php b/includes/discovery/vlans.inc.php index 48595adaf4..099bd3ff38 100644 --- a/includes/discovery/vlans.inc.php +++ b/includes/discovery/vlans.inc.php @@ -8,6 +8,7 @@ foreach ($vlans_db_raw as $vlan_db) { // Create an empty array to record what VLANs we discover this session. $device['vlans'] = array(); +$valid_vlan_port_ids = array(); require 'includes/discovery/vlans/q-bridge-mib.inc.php'; require 'includes/discovery/vlans/cisco-vtp.inc.php'; @@ -15,11 +16,8 @@ require 'includes/discovery/vlans/cisco-vtp.inc.php'; // Fetch switchport <> VLAN relationships. This is DIRTY. foreach ($device['vlans'] as $domain_id => $vlans) { foreach ($vlans as $vlan_id => $vlan) { - // Pull Tables for this VLAN - // /usr/bin/snmpbulkwalk -v2c -c kglk5g3l454@988 -OQUs -m BRIDGE-MIB -M /opt/librenms/mibs/ udp:sw2.ahf:161 dot1dStpPortEntry - // /usr/bin/snmpbulkwalk -v2c -c kglk5g3l454@988 -OQUs -m BRIDGE-MIB -M /opt/librenms/mibs/ udp:sw2.ahf:161 dot1dBasePortEntry // FIXME - do this only when vlan type == ethernet? - if (is_numeric($vlan_id) && ($vlan_id < 1002 || $vlan_id > 1105)) { + if (is_numeric($vlan_id) && ($vlan_id < 1002 || $vlan_id > 1005)) { // Ignore reserved VLAN IDs if ($device['os_group'] == 'cisco' || $device['os'] == 'ios') { // This shit only seems to work on IOS @@ -27,6 +25,8 @@ foreach ($device['vlans'] as $domain_id => $vlans) { $vlan_device = array_merge($device, array('community' => $device['community'].'@'.$vlan_id)); $vlan_data = snmpwalk_cache_oid($vlan_device, 'dot1dStpPortEntry', array(), 'BRIDGE-MIB:Q-BRIDGE-MIB'); $vlan_data = snmpwalk_cache_oid($vlan_device, 'dot1dBasePortEntry', $vlan_data, 'BRIDGE-MIB:Q-BRIDGE-MIB'); + } elseif (isset($qbridge_data)) { + $vlan_data = $qbridge_data[$vlan_id]; } echo "VLAN $vlan_id \n"; @@ -49,18 +49,21 @@ foreach ($device['vlans'] as $domain_id => $vlans) { $db_a['priority'] = isset($vlan_port['dot1dStpPortPriority']) ? $vlan_port['dot1dStpPortPriority'] : 0; $db_a['state'] = isset($vlan_port['dot1dStpPortState']) ? $vlan_port['dot1dStpPortState'] : 'unknown'; $db_a['cost'] = isset($vlan_port['dot1dStpPortPathCost']) ? $vlan_port['dot1dStpPortPathCost'] : 0; + $db_a['untagged'] = isset($vlan_port['untagged']) ? $vlan_port['untagged'] : 0; $from_db = dbFetchRow('SELECT * FROM `ports_vlans` WHERE device_id = ? AND port_id = ? AND `vlan` = ?', array($device['device_id'], $port['port_id'], $vlan_id)); if ($from_db['port_vlan_id']) { - dbUpdate($db_a, 'ports_vlans', '`port_vlan_id` = ?', array($from_db['port_vlan_id'])); + $db_id = $from_db['port_vlan_id']; + dbUpdate($db_a, 'ports_vlans', '`port_vlan_id` = ?', array($db_id)); echo 'Updated'; } else { - dbInsert(array_merge($db_w, $db_a), 'ports_vlans'); + $db_id = dbInsert(array_merge($db_w, $db_a), 'ports_vlans'); echo 'Inserted'; } + $valid_vlan_port_ids[] = $db_id; - echo "\n"; + echo PHP_EOL; }//end foreach }//end if }//end foreach @@ -74,4 +77,8 @@ foreach ($vlans_db as $domain_id => $vlans) { } } +// remove non-existent port-vlan mappings +$num = dbDelete('ports_vlans', '`device_id`=? AND `port_vlan_id` NOT IN ('.join(',', $valid_vlan_port).')', array($device['device_id'])); +d_echo("Deleted $num vlan mappings\n"); + unset($device['vlans']); diff --git a/includes/discovery/vlans/cisco-vtp.inc.php b/includes/discovery/vlans/cisco-vtp.inc.php index d1fa2f3cc7..3ae87d8bc1 100644 --- a/includes/discovery/vlans/cisco-vtp.inc.php +++ b/includes/discovery/vlans/cisco-vtp.inc.php @@ -1,7 +1,7 @@ $vtpdomain) { - echo 'VTP Domain '.$vtpdomain_id.' '.$vtpdomain['managementDomainName'].' '; + echo 'VTP Domain '.$vtpdomain_id.' '.$vtpdomain['managementDomainName'].' '; foreach ($vlans[$vtpdomain_id] as $vlan_id => $vlan) { - echo " $vlan_id"; + d_echo(" $vlan_id"); if (is_array($vlans_db[$vtpdomain_id][$vlan_id])) { echo '.'; } else { @@ -22,7 +22,7 @@ if ($device['os_group'] == 'cisco') { } $device['vlans'][$vtpdomain_id][$vlan_id] = $vlan_id; } + echo PHP_EOL; } } - echo "\n"; } diff --git a/includes/discovery/vlans/q-bridge-mib.inc.php b/includes/discovery/vlans/q-bridge-mib.inc.php index 8e6403cfdd..5d918d0d91 100644 --- a/includes/discovery/vlans/q-bridge-mib.inc.php +++ b/includes/discovery/vlans/q-bridge-mib.inc.php @@ -1,26 +1,43 @@ $vlan) { - echo " $vlan_id"; + d_echo(" $vlan_id"); if (is_array($vlans_db[$vtpdomain_id][$vlan_id])) { echo '.'; } else { - dbInsert(array('device_id' => $device['device_id'], 'vlan_domain' => $vtpdomain_id, 'vlan_vlan' => $vlan_id, 'vlan_name' => $vlan['dot1qVlanStaticName'], 'vlan_type' => array('NULL')), 'vlans'); + dbInsert(array( + 'device_id' => $device['device_id'], + 'vlan_domain' => $vtpdomain_id, + 'vlan_vlan' => $vlan_id, + 'vlan_name' => $vlan['dot1qVlanStaticName'], + 'vlan_type' => array('NULL') + ), 'vlans'); echo '+'; } $device['vlans'][$vtpdomain_id][$vlan_id] = $vlan_id; + + $id = "0.$vlan_id"; + $untagged_indexes = q_bridge_bits2indices($untag[$id]['dot1qVlanCurrentUntaggedPorts']); + $egress_indexes = q_bridge_bits2indices($tagoruntag[$id]['dot1qVlanCurrentEgressPorts']); + + foreach ($egress_indexes as $port_index) { + $qbridge_data[$vlan_id][$port_index] = $base_indexes[$port_index]; + $qbridge_data[$vlan_id][$port_index]['untagged'] = (in_array($port_index, $untagged_indexes) ? 1 : 0); + } } } - -echo "\n"; +echo PHP_EOL; diff --git a/includes/functions.php b/includes/functions.php index 3e6d52e963..bb8bacddf3 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -1647,3 +1647,41 @@ function getCIMCentPhysical($location, &$entphysical, &$index) return $index; } // end if - Level 1 } // end function + + +/* idea from http://php.net/manual/en/function.hex2bin.php comments */ +function hex2bin_compat($str) +{ + if (strlen($str) % 2 !== 0) { + trigger_error(__FUNCTION__.'(): Hexadecimal input string must have an even length', E_USER_WARNING); + } + return pack("H*", $str); +} + +if (!function_exists('hex2bin')) { + // This is only a hack + function hex2bin($str) + { + return hex2bin_compat($str); + } +} + +function q_bridge_bits2indices($hex_data) +{ + /* convert hex string to an array of 1-based indices of the nonzero bits + * ie. '9a00' -> '100110100000' -> array(1, 4, 5, 7) + */ + $hex_data = str_replace(' ', '', $hex_data); + $value = hex2bin($hex_data); + $length = strlen($value); + $indices = array(); + for ($i = 0; $i < $length; $i++) { + $byte = ord($value[$i]); + for ($j = 7; $j >= 0; $j--) { + if ($byte & (1 << $j)) { + $indices[] = 8*$i + 8-$j; + } + } + } + return $indices; +} diff --git a/sql-schema/144.sql b/sql-schema/144.sql new file mode 100644 index 0000000000..55fc155577 --- /dev/null +++ b/sql-schema/144.sql @@ -0,0 +1 @@ +ALTER TABLE `ports_vlans` ADD `untagged` TINYINT NOT NULL DEFAULT '0' AFTER `cost`; diff --git a/tests/VlanFunctionsTest.php b/tests/VlanFunctionsTest.php new file mode 100644 index 0000000000..c287dd41be --- /dev/null +++ b/tests/VlanFunctionsTest.php @@ -0,0 +1,47 @@ +. + * + * @package LibreNMS + * @link http://librenms.org + * @copyright 2016 Tony Murray + * @author Tony Murray + */ + +namespace LibreNMS\Tests; + +include 'includes/discovery/vlans/vlan_functions.inc.php'; + +class VlanFunctionsTest extends \PHPUnit_Framework_TestCase +{ + public function testQBridgeBits2Indices() + { + $bits = "8040 201008040201 ff000000 000000"; + $indices = array(1, 10, 19, 28, 37, 46, 55, 64, 65, 66, 67, 68, 69, 70, 71, 72); + + $this->assertTrue(q_bridge_bits2indices($bits) == $indices); + } + public function testHex2Bin() + { + $hexstr = "54686973206973206f6e6c79206120746573742e00ff"; + $binstr = "This is only a test.\x00\xff"; + + $this->assertTrue(hex2bin($hexstr) === $binstr); + $this->assertTrue(hex2bin_compat($hexstr) === $binstr); + } +}