diff --git a/contrib/findit b/contrib/findit index 6c89281ca..14151b2f9 100755 --- a/contrib/findit +++ b/contrib/findit @@ -5,6 +5,7 @@ find . \ -path ./html/includes/geshi -prune -o \ -path ./html/includes/jpgraph -prune -o \ -path ./html/js/jqplot -prune -o \ + -path ./junk -prune -o \ -path ./logs -prune -o \ -path ./mibs -prune -o \ -path ./rrd -prune -o \ diff --git a/doc/General/MIB-based-polling.md b/doc/General/MIB-based-polling.md new file mode 100644 index 000000000..e66461601 --- /dev/null +++ b/doc/General/MIB-based-polling.md @@ -0,0 +1,102 @@ +## WARNING ## + +MIB-based polling is experimental. It might overload your LibreNMS server, +destroy your data, set your routers on fire, and kick your cat. It has been +tested against a very limited set of devices (namely Ruckus ZD1000 wireless +controllers, and Net-SNMP on Linux). It may fail badly on other hardware. + +The approach taken is fairly basic and I claim no special expertise in +understanding MIBs. Most of my understanding of SNMP comes from reading +net-snmp man pages, and reverse engineering the output of snmptranslate and +snmpwalk and trying to make devices work with LibreNMS. I may have made +false assumptions and probably use wrong terminology in many places. Feel +free to offer corrections/suggestions via pull requests or email. + +Paul Gear + +## Overview ## + +MIB-based polling is disabled by default; you must set + `$config['poller_modules']['mib'] = 1;` +in config.php to enable it. + +The components involved in of MIB-based support are: + +### Discovery ### + + - MIB-based detection is not involved; any work done here would have to be + duplicated by the poller and thus would only increase load. + +### Polling ### + + - The file `includes/snmp.inc.php` now contains code which can parse MIBs + using `snmptranslate` and use the data returned to populate an array + which guides the poller in what to store. At the moment, only OIDs with + Unsigned32 and Counter64 data types are parsed. + - `includes/polling/mib.inc.php` looks for a MIB matching sysObjectID in + the MIB directory; if one is found, it: + - parses it + - walks that MIB on the device + - stores any numeric results in individual RRD files + - updates/adds graph definitions in the previously-unused graph_types + database table + - Individual OSes (`includes/polling/os/*.inc.php`) can poll extra MIBs + for a given OS by calling poll_mib(). At the moment, this actually + happens before the general MIB polling. + - Devices may be excluded from MIB polling by changing the setting in the + device edit screen (`/device/device=ID/tab=edit/section=modules/`) + +### Graphing ### + + - For each graph type defined in the database, a graph will appear in: + Device -> Graphs -> MIB + - MIB graphs are generated generically by + `html/includes/graphs/device/mib.inc.php` + - At the moment, all units are placed in the same graph. This is probably + non-optimal for, e.g., wifi controllers with hundreds of APs attached. + +## Adding/testing other device types ## + +One of the goals of this work is to help take out the heavy lifting of +adding new device types. Even if you want fully customised graphs or +tables, you can use the automatic collection of MIBs to make it easy to +gather the data you want. + +### How to add a new device MIB ### + + 1. Ensure the manufacturer's MIB is present in the mibs directory. If you + plan to submit your work to LibreNMS, make sure you attribute the source + of the MIB, including the exact download URL. + 2. Check that `snmptranslate -Ts -M mibs -m MODULE | grep mibName` produces + a named list of OIDs. See the comments for `snmp_mib_walk()` in + `includes/snmp.inc.php` for an example. + 3. Check that `snmptranslate -Td -On -M mibs -m MODULE MODULE::mibName` + produces a parsed description of the OID values. An example can be + found in the comments for `snmp_mib_parse()` in `includes/snmp.inc.php`. + 4. Get the `sysObjectID` from a device, for example:``` +snmpget -v2c -c public -OUsb -m SNMPv2-MIB -M /opt/librenms/mibs -t 30 hostname sysObjectID.0 +``` + 5. Ensure `snmptranslate -m all -M /opt/librenms/mibs OID 2>/dev/null` + (where OID is the value returned for sysObjectID above) results in a + valid name for the MIB. See the comments for `snmp_translate()` in + `includes/snmp.inc.php` for an example. If this step fails, it means + there is something wrong with the MIB and net-snmp cannot parse it. + 6. Add any additional MIBs you wish to poll for specific device types to + `includes/polling/os/OSNAME.inc.php` by calling `poll_mibs()` with the + MIB module and name. See includes/polling/os/ruckuswireless.inc.php for + an example. + 7. That should be all you need to see MIB graphs! + +## TODO ## + + - Save the most recent MIB data in the database (including string types + which cannot be graphed). Display it in the appropriate places. + - Parse and save integer and timetick data types. + - Filter MIBs/OIDs from being polled and/or saved. + - Move graphs from the MIB section to elsewhere. e.g. There is already + specific support for wireless APs - this should be utilised, but isn't + yet. + - Combine multiple MIB values into graphs automatically on a predefined or + user-defined basis. + - Include MIB types in stats submissions. + diff --git a/doc/Support/FAQ.md b/doc/Support/FAQ.md index ef6d6232e..4fb4d74cb 100644 --- a/doc/Support/FAQ.md +++ b/doc/Support/FAQ.md @@ -103,7 +103,8 @@ To go into a bit more detail, the following are usually needed: **includes/definitions.inc.php** Update this file to include the required definitions for the new OS. **includes/discovery/os/ciscowlc.inc.php** -This file just sets the $os variable, done by checking the sysDescr snmp value for a particular value that matches the OS you are adding. +This file just sets the $os variable, done by checking the SNMP tree for a particular value that matches the OS you are adding. Typically, this will come from the presence of specific values in +sysObjectID or sysDescr, or the existence of a particular enterprise tree. **includes/polling/os/ciscowlc.inc.php** This file will usually set the variables for $version and $hardware gained from an snmp lookup. **html/images/os/$os.png** diff --git a/doc/Support/Poller Support.md b/doc/Support/Poller Support.md index 4e06b54df..616698630 100644 --- a/doc/Support/Poller Support.md +++ b/doc/Support/Poller Support.md @@ -70,6 +70,7 @@ $config['poller_modules']['aruba-controller'] = 1; $config['poller_modules']['entity-physical'] = 1; $config['poller_modules']['applications'] = 1; $config['poller_modules']['cisco-asa-firewall'] = 1; +$config['poller_modules']['mib'] = 0; ``` #### Poller modules @@ -112,7 +113,7 @@ $config['poller_modules']['cisco-asa-firewall'] = 1; `ospf`: OSPF Support. -`cisco-ipsec-flow-monitor': IPSec statistics support. +`cisco-ipsec-flow-monitor`: IPSec statistics support. `cisco-remote-access-monitor`: Cisco remote access support. @@ -138,6 +139,8 @@ $config['poller_modules']['cisco-asa-firewall'] = 1; `cisco-asa-firewall`: Cisco ASA firewall support. +`mib`: Support for generic MIB parsing. + #### Running Here are some examples of running poller from within your install directory. @@ -176,4 +179,4 @@ DB Updates RRD Updates -SNMP Response \ No newline at end of file +SNMP Response diff --git a/html/includes/api_functions.inc.php b/html/includes/api_functions.inc.php index d4dce5e87..d2e9f3061 100644 --- a/html/includes/api_functions.inc.php +++ b/html/includes/api_functions.inc.php @@ -436,6 +436,7 @@ function get_graphs() { $router = $app->router()->getCurrentRoute()->getParams(); $hostname = $router['hostname']; + // FIXME: this has some overlap with html/pages/device/graphs.inc.php // use hostname as device_id if it's all digits $device_id = ctype_digit($hostname) ? $hostname : getidbyname($hostname); $graphs = array(); diff --git a/html/includes/front/boxes.inc.php b/html/includes/front/boxes.inc.php index 0d6f2a939..665c1d738 100644 --- a/html/includes/front/boxes.inc.php +++ b/html/includes/front/boxes.inc.php @@ -2,6 +2,7 @@ /* * LibreNMS front page graphs * + * Author: Paul Gear * Copyright (c) 2013 Gear Consulting Pty Ltd * * This program is free software: you can redistribute it and/or modify it diff --git a/html/includes/front/top_device_bits.inc.php b/html/includes/front/top_device_bits.inc.php index a44d3a1e8..4b48bfd4a 100644 --- a/html/includes/front/top_device_bits.inc.php +++ b/html/includes/front/top_device_bits.inc.php @@ -3,6 +3,7 @@ * LibreNMS front page top devices graph * - Find most utilised devices that have been polled in the last N minutes * + * Author: Paul Gear * Copyright (c) 2013 Gear Consulting Pty Ltd * * This program is free software: you can redistribute it and/or modify it diff --git a/html/includes/front/top_ports.inc.php b/html/includes/front/top_ports.inc.php index 9a09f3057..008525207 100644 --- a/html/includes/front/top_ports.inc.php +++ b/html/includes/front/top_ports.inc.php @@ -3,6 +3,7 @@ * LibreNMS front page top ports graph * - Find most utilised ports that have been polled in the last N minutes * + * Author: Paul Gear * Copyright (c) 2013 Gear Consulting Pty Ltd * * This program is free software: you can redistribute it and/or modify it diff --git a/html/includes/graphs/device/auth.inc.php b/html/includes/graphs/device/auth.inc.php index 063baba3f..79027e7bf 100644 --- a/html/includes/graphs/device/auth.inc.php +++ b/html/includes/graphs/device/auth.inc.php @@ -2,9 +2,9 @@ if ($auth || device_permitted($device['device_id'])) { - $title = generate_device_link($device); - $graph_title = $device['hostname']; - $auth = TRUE; + $title = generate_device_link($device); + $graph_title = $device['hostname']; + $auth = true; } ?> diff --git a/html/includes/graphs/device/mib.inc.php b/html/includes/graphs/device/mib.inc.php new file mode 100644 index 000000000..7f76f3bbe --- /dev/null +++ b/html/includes/graphs/device/mib.inc.php @@ -0,0 +1,36 @@ + + * + * 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. + */ + +$rrd_list = array(); +$prefix = rrd_name($device['hostname'], array($subtype, ""), ""); +foreach (glob($prefix."*.rrd") as $filename) { + // find out what * expanded to + $globpart = str_replace($prefix, '', $filename); // take off the prefix + $instance = substr($globpart, 0, -4); // take off ".rrd" + + $ds = array(); + $mibparts = explode("-", $subtype); + $mibvar = end($mibparts); + $ds['ds'] = name_shorten($mibvar); + $ds['descr'] = "$mibvar-$instance"; + $ds['filename'] = $filename; + $rrd_list[] = $ds; +} + +$colours = 'mixed'; +$scale_min = "0"; +$nototal = 0; +$simple_rrd = true; + +include("includes/graphs/generic_multi_line.inc.php"); diff --git a/html/includes/graphs/graph.inc.php b/html/includes/graphs/graph.inc.php index 7e609b6ed..5c6bfe1c1 100644 --- a/html/includes/graphs/graph.inc.php +++ b/html/includes/graphs/graph.inc.php @@ -38,37 +38,16 @@ $graphfile = $config['temp_dir'] . "/" . strgen() . ".png"; $type = $graphtype['type']; $subtype = $graphtype['subtype']; -if (is_file($config['install_dir'] . "/html/includes/graphs/$type/$subtype.inc.php")) -{ +$auth = is_client_authorized($_SERVER['REMOTE_ADDR']); +include($config['install_dir'] . "/html/includes/graphs/$type/auth.inc.php"); - if (isset($config['allow_unauth_graphs']) && $config['allow_unauth_graphs']) - { - $auth = "1"; // hardcode auth for all with config function - } - - if (isset($config['allow_unauth_graphs_cidr']) && count($config['allow_unauth_graphs_cidr']) > 0) - { - foreach ($config['allow_unauth_graphs_cidr'] as $range) - { - if (Net_IPv4::ipInNetwork($_SERVER['REMOTE_ADDR'], $range)) - { - $auth = "1"; - if ($debug) { echo("matched $range"); } - break; - } - } - } - - include($config['install_dir'] . "/html/includes/graphs/$type/auth.inc.php"); - - if (isset($auth) && $auth) - { +if ($auth === true && is_file($config['install_dir'] . "/html/includes/graphs/$type/$subtype.inc.php")) { include($config['install_dir'] . "/html/includes/graphs/$type/$subtype.inc.php"); - } - } -else -{ +elseif ($auth === true && is_mib_graph($type, $subtype)) { + include($config['install_dir'] . "/html/includes/graphs/$type/mib.inc.php"); +} +else { graph_error("$type*$subtype ");//Graph Template Missing"); } diff --git a/html/pages/device/graphs.inc.php b/html/pages/device/graphs.inc.php index 3be4ad7e7..a275e71e3 100644 --- a/html/pages/device/graphs.inc.php +++ b/html/pages/device/graphs.inc.php @@ -1,6 +1,5 @@ 'device', @@ -15,19 +14,19 @@ print_optionbar_start(); echo("Graphs » "); -$sep = ""; - foreach (dbFetchRows("SELECT * FROM device_graphs WHERE device_id = ? ORDER BY graph", array($device['device_id'])) as $graph) { $section = $config['graph_types']['device'][$graph['graph']]['section']; - $graph_enable[$section][$graph['graph']] = $graph['graph']; + if ($section != "") { + $graph_enable[$section][$graph['graph']] = $graph['graph']; + } } // These are standard graphs we should have for all systems $graph_enable['poller']['poller_perf'] = 'device_poller_perf'; $graph_enable['poller']['ping_perf'] = 'device_ping_perf'; -#foreach ($config['graph_sections'] as $section) +$sep = ""; foreach ($graph_enable as $section => $nothing) { if (isset($graph_enable) && is_array($graph_enable[$section])) @@ -47,8 +46,8 @@ foreach ($graph_enable as $section => $nothing) $sep = " | "; } } - unset ($sep); + print_optionbar_end(); $graph_enable = $graph_enable[$vars['group']]; diff --git a/html/pages/device/munin.inc.php b/html/pages/device/munin.inc.php index a061530da..67fd22d6e 100644 --- a/html/pages/device/munin.inc.php +++ b/html/pages/device/munin.inc.php @@ -1,6 +1,5 @@ 'device', diff --git a/html/pages/graphs.inc.php b/html/pages/graphs.inc.php index 89c47aeec..8e438489d 100644 --- a/html/pages/graphs.inc.php +++ b/html/pages/graphs.inc.php @@ -45,20 +45,6 @@ if (!$auth) $title .= " :: ".ucfirst($subtype); } - # Load our list of available graphtypes for this object - // FIXME not all of these are going to be valid - if ($handle = opendir($config['install_dir'] . "/html/includes/graphs/".$type."/")) - { - while (false !== ($file = readdir($handle))) - { - if ($file != "." && $file != ".." && $file != "auth.inc.php" &&strstr($file, ".inc.php")) - { - $types[] = str_replace(".inc.php", "", $file); - } - } - closedir($handle); - } - $graph_array = $vars; $graph_array['height'] = "60"; $graph_array['width'] = $thumb_width; @@ -75,11 +61,14 @@ if (!$auth) onchange="window.open(this.options[this.selectedIndex].value,'_top')" > $type."_".$avail_type, 'page' => "graphs"))."'"); - if ($avail_type == $subtype) { echo(" selected"); } - echo(">".nicecase($avail_type).""); + if ($avail_type == $subtype) { + echo(" selected"); + } + $display_type = is_mib_graph($type, $avail_type) ? $avail_type : nicecase($avail_type); + echo(">$display_type"); } ?> diff --git a/includes/common.php b/includes/common.php index d14395e7f..8e7499841 100644 --- a/includes/common.php +++ b/includes/common.php @@ -1,6 +1,20 @@ + * Copyright (c) 2014-2015 Gear Consulting Pty Ltd + * + * 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. + */ function format_number_short($number, $sf) { @@ -476,6 +490,19 @@ function get_dev_attrib($device, $attrib_type) } } +function is_dev_attrib_enabled($device, $attrib, $default = true) +{ + $val = get_dev_attrib($device, $attrib); + if ($val != NULL) { + // attribute is set + return ($val != 0); + } + else { + // attribute not set + return $default; + } +} + function del_dev_attrib($device, $attrib_type) { return dbDelete('devices_attribs', "`device_id` = ? AND `attrib_type` = ?", array($device['device_id'], $attrib_type)); @@ -592,4 +619,132 @@ function edit_service($service, $descr, $service_ip, $service_param = "", $servi } +/* + * convenience function - please use this instead of 'if ($debug) { echo ...; }' + */ +function d_echo($text, $no_debug_text = null) +{ + global $debug; + if ($debug) { + echo "$text"; + } + elseif ($no_debug_text) { + echo "$no_debug_text"; + } +} + +/* + * convenience function - please use this instead of 'if ($debug) { print_r ...; }' + */ +function d_print_r($var, $no_debug_text = null) +{ + global $debug; + if ($debug) { + print_r($var); + } + elseif ($no_debug_text) { + echo "$no_debug_text"; + } +} + +/* + * Shorten the name so it works as an RRD data source name (limited to 19 chars by default). + * Substitute for $subst if necessary. + * @return the shortened name + */ +function name_shorten($name, $common = null, $subst = "mibval", $len = 19) +{ + if ($common !== null) { + // remove common from the beginning of the string, if present + if (strlen($name) > $len && strpos($name, $common) >= 0) { + $newname = str_replace($common, '', $name); + $name = $newname; + } + } + if (strlen($name) > $len) { + $name = $subst; + } + return $name; +} + +/* + * @return the name of the rrd file for $host's $extra component + * @param host Host name + * @param extra Components of RRD filename - will be separated with "-" + */ +function rrd_name($host, $extra, $exten = ".rrd") +{ + global $config; + $filename = safename(is_array($extra) ? implode("-", $extra) : $extra); + return implode("/", array($config['rrd_dir'], $host, $filename.$exten)); +} + +/* + * @return true if the given graph type is a dynamic MIB graph + */ +function is_mib_graph($type, $subtype) +{ + global $config; + return $config['graph_types'][$type][$subtype]['section'] == 'mib'; +} + +/* + * @return true if client IP address is authorized to access graphs + */ +function is_client_authorized($clientip) +{ + global $config; + + if (isset($config['allow_unauth_graphs']) && $config['allow_unauth_graphs']) { + d_echo("Unauthorized graphs allowed\n"); + return true; + } + + if (isset($config['allow_unauth_graphs_cidr'])) { + foreach ($config['allow_unauth_graphs_cidr'] as $range) { + if (Net_IPv4::ipInNetwork($clientip, $range)) { + d_echo("Unauthorized graphs allowed from $range\n"); + return true; + } + } + } + + return false; +} + +/* + * @return an array of all graph subtypes for the given type + * FIXME not all of these are going to be valid + */ +function get_graph_subtypes($type) +{ + global $config; + + $types = array(); + + // find the subtypes defined in files + if ($handle = opendir($config['install_dir'] . "/html/includes/graphs/$type/")) { + while (false !== ($file = readdir($handle))) { + if ($file != "." && $file != ".." && $file != "auth.inc.php" && strstr($file, ".inc.php")) { + $types[] = str_replace(".inc.php", "", $file); + } + } + closedir($handle); + } + + // find the MIB subtypes + foreach ($config['graph_types'] as $type => $unused1) { + print_r($type); + foreach ($config['graph_types'][$type] as $subtype => $unused2) { + print_r($subtype); + if (is_mib_graph($type, $subtype)) { + $types[] = $subtype; + } + } + } + + sort($types); + return $types; +} + ?> diff --git a/includes/defaults.inc.php b/includes/defaults.inc.php index 739cc1127..8c02f3779 100644 --- a/includes/defaults.inc.php +++ b/includes/defaults.inc.php @@ -533,6 +533,7 @@ $config['poller_modules']['aruba-controller'] = 1; $config['poller_modules']['entity-physical'] = 1; $config['poller_modules']['applications'] = 1; $config['poller_modules']['cisco-asa-firewall'] = 1; +$config['poller_modules']['mib'] = 0; // List of discovery modules. Need to be in this array to be // considered for execution. diff --git a/includes/definitions.inc.php b/includes/definitions.inc.php index 090702c33..1fb938f36 100644 --- a/includes/definitions.inc.php +++ b/includes/definitions.inc.php @@ -1176,7 +1176,7 @@ foreach ($config['os'] as $this_os => $blah) // Graph Types -$config['graph_sections'] = array('general', 'system', 'firewall', 'netstats', 'wireless', 'storage', 'vpdn', 'load balancer'); +include_once($config['install_dir'] . "/includes/load_db_graph_types.inc.php"); // Device - Wireless - AirMAX diff --git a/includes/functions.php b/includes/functions.php index bcde5c598..96d6a5227 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -1247,23 +1247,3 @@ function ip_exists($ip) { } return true; } - -/* - * convenience function - please use this instead of 'if ($debug) { echo ...; }' - */ -function d_echo($text) { - global $debug; - if ($debug) { - echo "$text\n"; - } -} - -/* - * convenience function - please use this instead of 'if ($debug) { print_r ...; }' - */ -function d_print_r($var) { - global $debug; - if ($debug) { - print_r($var); - } -} diff --git a/includes/load_db_graph_types.inc.php b/includes/load_db_graph_types.inc.php new file mode 100644 index 000000000..618a49b8c --- /dev/null +++ b/includes/load_db_graph_types.inc.php @@ -0,0 +1,23 @@ + + * + * 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. + */ + +// load graph types from the database +foreach (dbFetchRows('select * from graph_types') as $graph) { + // remove leading 'graph_' from column names + foreach ($graph as $k => $v) { + $key = strpos($k, 'graph_') == 0 ? str_replace('graph_', '', $k) : $k; + $g[$key] = $v; + } + $config['graph_types'][$g['type']][$g['subtype']] = $g; +} diff --git a/includes/polling/functions.inc.php b/includes/polling/functions.inc.php index a2ec23be9..3067d1cb7 100644 --- a/includes/polling/functions.inc.php +++ b/includes/polling/functions.inc.php @@ -183,6 +183,7 @@ function poll_device($device, $options) { if ($attribs['poll_'.$module] || ( $module_status && !isset($attribs['poll_'.$module]))) { + // TODO per-module polling stats include('includes/polling/'.$module.'.inc.php'); } elseif (isset($attribs['poll_'.$module]) && $attribs['poll_'.$module] == "0") { echo("Module [ $module ] disabled on host.\n"); @@ -201,11 +202,11 @@ function poll_device($device, $options) foreach (dbFetch("SELECT `graph` FROM `device_graphs` WHERE `device_id` = ?", array($device['device_id'])) as $graph) { - if (!isset($graphs[$graph["graph"]])) + if (isset($graphs[$graph["graph"]])) { - dbDelete('device_graphs', "`device_id` = ? AND `graph` = ?", array($device['device_id'], $graph["graph"])); - } else { $oldgraphs[$graph["graph"]] = TRUE; + } else { + dbDelete('device_graphs', "`device_id` = ? AND `graph` = ?", array($device['device_id'], $graph["graph"])); } } @@ -222,6 +223,7 @@ function poll_device($device, $options) $device_end = utime(); $device_run = $device_end - $device_start; $device_time = substr($device_run, 0, 5); + // TODO: These should be easy converts to rrd_create_update() // Poller performance rrd $poller_rrd = $config['rrd_dir'] . "/" . $device['hostname'] . "/poller-perf.rrd"; if (!is_file($poller_rrd)) @@ -266,7 +268,7 @@ function poll_mib_def($device, $mib_name_table, $mib_subdir, $mib_oids, $mib_gra global $config; - echo("This is mag_poll_mib_def Processing\n"); + echo("This is poll_mib_def Processing\n"); $mib = NULL; if (stristr($mib_name_table, "UBNT")) { @@ -346,4 +348,26 @@ function poll_mib_def($device, $mib_name_table, $mib_subdir, $mib_oids, $mib_gra return TRUE; } +/* + * Please use this instead of creating & updating RRD files manually. + * @param device Device object - only 'hostname' is used at present + * @param name Array of rrdname components + * @param def Array of data definitions + * @param val Array of value definitions + * + */ +function rrd_create_update($device, $name, $def, $val, $step = 300) +{ + global $config; + $rrd = rrd_name($device['hostname'], $name); + + if (!is_file($rrd) && $def != null) { + // add the --step and the rra definitions to the array + $newdef = "--step $step ".implode(' ', $def).$config['rrd_rra']; + rrdtool_create($rrd, $newdef); + } + + rrdtool_update($rrd, $val); +} + ?> diff --git a/includes/polling/mib.inc.php b/includes/polling/mib.inc.php new file mode 100644 index 000000000..07d6c16fc --- /dev/null +++ b/includes/polling/mib.inc.php @@ -0,0 +1,16 @@ + + * + * 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. + */ + +$devicemib = array($device['sysObjectID'] => "all"); +poll_mibs($devicemib, $device, $graphs); diff --git a/includes/polling/os/ruckuswireless.inc.php b/includes/polling/os/ruckuswireless.inc.php index 1c9b1b0ec..d864e0b7b 100644 --- a/includes/polling/os/ruckuswireless.inc.php +++ b/includes/polling/os/ruckuswireless.inc.php @@ -2,8 +2,12 @@ /* * LibreNMS Ruckus Wireless OS information module * +* Originally by: * Copyright (c) 2015 Søren Friis Rosiak +* +* Updates by Paul Gear: * Copyright (c) 2015 Gear Consulting Pty Ltd +* * 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 @@ -39,4 +43,12 @@ $ruckuscountry = first_oid_match($device, $ruckuscountries); if (isset($ruckuscountry) && $ruckuscountry != "") { $version .= " ($ruckuscountry)"; } + +$ruckus_mibs = array( + "ruckusZDSystemStats" => "RUCKUS-ZD-SYSTEM-MIB", + "ruckusZDWLANTable" => "RUCKUS-ZD-WLAN-MIB", + "ruckusZDWLANAPTable" => "RUCKUS-ZD-WLAN-MIB", +); +poll_mibs($ruckus_mibs, $device, $graphs); + ?> diff --git a/includes/polling/system.inc.php b/includes/polling/system.inc.php index 4cf0c7754..95c43aa22 100644 --- a/includes/polling/system.inc.php +++ b/includes/polling/system.inc.php @@ -113,6 +113,12 @@ log_event("Contact -> ".$poll_device['sysContact'], $device, 'system'); } + if ($poll_device['sysObjectID'] && $poll_device['sysObjectID'] != $device['sysObjectID']) + { + $update_array['sysObjectID'] = $poll_device['sysObjectID']; + log_event("ObjectID -> ".$poll_device['sysObjectID'], $device, 'system'); + } + if ($poll_device['sysName'] && $poll_device['sysName'] != $device['sysName']) { $update_array['sysName'] = $poll_device['sysName']; diff --git a/includes/snmp.inc.php b/includes/snmp.inc.php index c06d71da7..ca4047815 100644 --- a/includes/snmp.inc.php +++ b/includes/snmp.inc.php @@ -322,9 +322,9 @@ function snmp_cache_ifIndex($device) return $array; } -function snmpwalk_cache_oid($device, $oid, $array, $mib = NULL, $mibdir = NULL) +function snmpwalk_cache_oid($device, $oid, $array, $mib = NULL, $mibdir = NULL, $snmpflags = "-OQUs") { - $data = snmp_walk($device, $oid, "-OQUs", $mib, $mibdir); + $data = snmp_walk($device, $oid, $snmpflags, $mib, $mibdir); foreach (explode("\n", $data) as $entry) { list($oid,$value) = explode("=", $entry, 2); @@ -345,20 +345,7 @@ function snmpwalk_cache_oid($device, $oid, $array, $mib = NULL, $mibdir = NULL) // to be the same. function snmpwalk_cache_oid_num($device, $oid, $array, $mib = NULL, $mibdir = NULL) { - $data = snmp_walk($device, $oid, "-OQUn", $mib, $mibdir); - - foreach (explode("\n", $data) as $entry) - { - list($oid,$value) = explode("=", $entry, 2); - $oid = trim($oid); $value = trim($value); - list($oid, $index) = explode(".", $oid, 2); - if (!strstr($value, "at this OID") && isset($oid) && isset($index)) - { - $array[$index][$oid] = $value; - } - } - - return $array; + return snmpwalk_cache_oid($device, $oid, $array, $mib, $mibdir, $snmpflags = "-OQUn"); } @@ -818,4 +805,301 @@ function snmp_gen_auth (&$device) return $cmd; } +/* + * Translate the given MIB into a vaguely useful PHP array. Each keyword becomes an array index. + * + * Example: + * snmptranslate -Td -On -M mibs -m RUCKUS-ZD-SYSTEM-MIB RUCKUS-ZD-SYSTEM-MIB::ruckusZDSystemStatsNumSta + * .1.3.6.1.4.1.25053.1.2.1.1.1.15.30 + * ruckusZDSystemStatsAllNumSta OBJECT-TYPE + * -- FROM RUCKUS-ZD-SYSTEM-MIB + * SYNTAX Unsigned32 + * MAX-ACCESS read-only + * STATUS current + * DESCRIPTION "Number of All client devices" + * ::= { iso(1) org(3) dod(6) internet(1) private(4) enterprises(1) ruckusRootMIB(25053) ruckusObjects(1) ruckusZD(2) ruckusZDSystemModule(1) ruckusZDSystemMIB(1) ruckusZDSystemObjects(1) + * ruckusZDSystemStats(15) 30 } + */ +function snmp_mib_parse($oid, $mib, $module, $mibdir = null) +{ + global $debug; + + $fulloid = explode(".", $oid); + $lastpart = end($fulloid); + + $cmd = "snmptranslate -Td -On"; + $cmd .= mibdir($mibdir); + $cmd .= " -m ".$module." ".$module."::"; + $cmd .= $lastpart; + + $result = array(); + $lines = preg_split('/\n+/', trim(shell_exec($cmd))); + foreach ($lines as $l) { + $f = preg_split('/\s+/', trim($l)); + // first line is all numeric + if (preg_match('/^[\d.]+$/', $f[0])) { + $result['oid'] = $f[0]; + continue; + } + // then the name of the object type + if ($f[1] && $f[1] == "OBJECT-TYPE") { + $result['object_type'] = $f[0]; + $result['shortname'] = str_replace($mib, '', $f[0]); + $result['dsname'] = name_shorten($f[0], $mib); + continue; + } + // then the other data elements + if ($f[0] == "--" && $f[1] == "FROM") { + $result['module'] = $f[2]; + continue; + } + if ($f[0] == "MAX-ACCESS") { + $result['max_access'] = $f[1]; + continue; + } + if ($f[0] == "STATUS") { + $result[strtolower($f[0])] = $f[1]; + continue; + } + if ($f[0] == "SYNTAX") { + $result[strtolower($f[0])] = $f[1]; + continue; + } + if ($f[0] == "DESCRIPTION") { + $desc = explode('"', $l); + if ($desc[1]) { + $str = preg_replace('/^[\s.]*/', '', $desc[1]); + $str = preg_replace('/[\s.]*$/', '', $str); + $result[strtolower($f[0])] = $str; + } + continue; + } + } + // The main mib entry doesn't have any useful data in it - only return items that have the syntax specified. + if (isset($result['syntax']) && isset($result['object_type'])) { + $result['mib'] = $mib; + return $result; + } + else { + return null; + } +} + + +/* + * Walks through the given MIB module, looking for the given MIB. + * NOTE: different from snmp walk - this doesn't touch the device. + * NOTE: There's probably a better way to do this with snmptranslate. + * + * Example: + * snmptranslate -Ts -M mibs -m RUCKUS-ZD-SYSTEM-MIB | grep ruckusZDSystemStats + * .iso.org.dod.internet.private.enterprises.ruckusRootMIB.ruckusObjects.ruckusZD.ruckusZDSystemModule.ruckusZDSystemMIB.ruckusZDSystemObjects.ruckusZDSystemStats + * .iso.org.dod.internet.private.enterprises.ruckusRootMIB.ruckusObjects.ruckusZD.ruckusZDSystemModule.ruckusZDSystemMIB.ruckusZDSystemObjects.ruckusZDSystemStats.ruckusZDSystemStatsNumAP + * .iso.org.dod.internet.private.enterprises.ruckusRootMIB.ruckusObjects.ruckusZD.ruckusZDSystemModule.ruckusZDSystemMIB.ruckusZDSystemObjects.ruckusZDSystemStats.ruckusZDSystemStatsNumSta + * ... + */ +function snmp_mib_walk($mib, $module, $mibdir = null) +{ + $cmd = "snmptranslate -Ts"; + $cmd .= mibdir($mibdir); + $cmd .= " -m ".$module; + $result = array(); + $data = preg_split('/\n+/', shell_exec($cmd)); + foreach ($data as $oid) { + // only include oids which are part of this mib + if (strstr($oid, $mib)) { + $obj = snmp_mib_parse($oid, $mib, $module, $mibdir); + if ($obj) { + $result[] = $obj; + } + } + } + return $result; +} + +/* + * @return an array containing all of the mib objects, keyed by object-type; + * returns an empty array if something goes wrong. + */ +function snmp_mib_load($mib, $module, $mibdir = null) +{ + $mibs = array(); + foreach (snmp_mib_walk($mib, $module, $mibdir) as $obj) { + $mibs[$obj['object_type']] = $obj; + } + return $mibs; +} + +/* + * Turn the given oid (name or numeric value) into a MODULE::mib name. + * @return an array consisting of the module and mib names, or null if no matching MIB is found. + * Example: + * snmptranslate -m all -M mibs .1.3.6.1.4.1.8072.3.2.10 2>/dev/null + * NET-SNMP-TC::linux + */ +function snmp_translate($oid, $module, $mibdir = null) +{ + if ($module !== "all") { + $oid = "$module::$oid"; + } + $cmd = "snmptranslate" . mibdir($mibdir); + $cmd .= " -m $module $oid"; // load all the MIBs looking for our object + $cmd .= " 2>/dev/null"; // ignore invalid MIBs + + $lines = preg_split('/\n+/', external_exec($cmd)); + if (empty($lines)) { + d_echo("No results from snmptranslate\n"); + return null; + } + + $matches = array(); + if (!preg_match('/(.*)::(.*)/', $lines[0], $matches)) { + d_echo("This doesn't look like a MIB: $lines[0]\n"); + return null; + } + + d_echo("SNMP translated: $module::$oid -> $matches[1]::$matches[2]\n"); + return array($matches[1], $matches[2]); +} + +/* + * check if the type of the oid is a numeric type, and if so, + * @return the name of RRD type that is best suited to saving it + */ +function oid_rrd_type($oid, $mibdef) { + if (!isset($mibdef[$oid])) { + return false; + } + switch ($mibdef[$oid]['syntax']) { + + case 'OCTET': + case 'IpAddress': + return false; + + case 'TimeTicks': + // Need to find a way to flag that this should be parsed + //return 'COUNTER'; + return false; + + case 'Counter64': + return 'COUNTER:600:0:U'; + + case 'Unsigned32': + return 'GAUGE:600:U:U'; + + } + return false; +} + +/* + * Construct a graph names for use in the database. + * Tag each as in use on this device in &$graphs. + * Update the database with graph definitions as needed. + * We don't include the index in the graph name - that is handled at display time. + */ +function tag_graphs($mibname, $oids, $mibdef, &$graphs) +{ + foreach ($oids as $index => $array) { + foreach ($array as $oid => $val) { + $graphname = $mibname."-".$mibdef[$oid]['shortname']; + $graphs[$graphname] = true; + } + } +} + +/* + * Ensure a graph_type definition exists in the database for the entities in this MIB + */ +function update_mib_graph_types($mibname, $oids, $mibdef, $graphs) +{ + $seengraphs = array(); + + // Get the list of graphs currently in the database + // FIXME: there's probably a more efficient way to do this + foreach (dbFetch("SELECT DISTINCT `graph_subtype` FROM `graph_types` WHERE `graph_subtype` LIKE ?", array("$mibname-%")) as $graph) { + $seengraphs[$graph['graph_subtype']] = true; + } + + foreach ($oids as $index => $array) { + $i = 1; + foreach ($array as $oid => $val) { + $graphname = "$mibname-".$mibdef[$oid]['shortname']; + + // add the graph if it's not in the database already + if ($graphs[$graphname] && !$seengraphs[$graphname]) { + // construct a graph definition based on the MIB definition + $graphdef = array(); + $graphdef['graph_type'] = "device"; + $graphdef['graph_subtype'] = $graphname; + $graphdef['graph_section'] = "mib"; + $graphdef['graph_descr'] = $mibdef[$oid]['description']; + $graphdef['graph_order'] = $i++; + // TODO: add colours, unit_text, and ds + + // add graph to the database + dbInsert($graphdef, 'graph_types'); + } + } + } +} + +/* + * Save all of the measurable oids for the device in their own RRDs. + */ +function save_mibs($device, $mibname, $oids, $mibdef, &$graphs) +{ + $usedoids = array(); + foreach ($oids as $index => $array) { + foreach ($array as $oid => $val) { + $type = oid_rrd_type($oid, $mibdef); + if ($type === false) { + continue; + } + $usedoids[$index][$oid] = $val; + rrd_create_update( + $device, + array($mibname, $mibdef[$oid]['shortname'], $index), + array("DS:".$mibdef[$oid]['dsname'].":$type"), + "N:$val" + ); + } + } + tag_graphs($mibname, $usedoids, $mibdef, $graphs); + update_mib_graph_types($mibname, $usedoids, $mibdef, $graphs); +} + +/* + * Take a list of MIB name => module pairs. + * Validate MIBs and poll based on the results. + */ +function poll_mibs($list, $device, &$graphs) +{ + if (!is_dev_attrib_enabled($device, "poll_mib")) { + d_echo("MIB module disabled for ".$device['hostname']."\n"); + return; + } + $mibdefs = array(); + echo("MIB-based polling:"); + d_echo("\n"); + + foreach ($list as $name => $module) { + $translated = snmp_translate($name, $module); + if ($translated) { + echo(" $module::$name"); + d_echo("\n"); + $mod = $translated[0]; + $nam = $translated[1]; + $mibdefs[$nam] = snmp_mib_load($nam, $mod); + $oids = snmpwalk_cache_oid($device, $nam, array(), $mod, null, "-OQUsb"); + d_print_r($oids); + save_mibs($device, $nam, $oids, $mibdefs[$nam], $graphs); + } + else { + d_echo("MIB: no match for $module::$name\n"); + } + } + d_echo("Done MIB-based polling"); + echo("\n"); +} + ?> diff --git a/sql-schema/054.sql b/sql-schema/054.sql new file mode 100644 index 000000000..5122975a7 --- /dev/null +++ b/sql-schema/054.sql @@ -0,0 +1,4 @@ +ALTER TABLE `graph_types` CHANGE `graph_subtype` `graph_subtype` varchar(64); +ALTER TABLE `device_graphs` CHANGE `graph` `graph` varchar(64); +ALTER TABLE `graph_types` CHANGE `graph_descr` `graph_descr` varchar(255); +ALTER TABLE `graph_types` ADD PRIMARY KEY (`graph_type`, `graph_subtype`, `graph_section`);