diff --git a/doc/Extensions/Applications.md b/doc/Extensions/Applications.md index fa09fceb4f..8077617065 100644 --- a/doc/Extensions/Applications.md +++ b/doc/Extensions/Applications.md @@ -1074,33 +1074,46 @@ extend icecast /etc/snmp/icecast-stats.sh A small python3 script that reports current DHCP leases stats and pool usage of ISC DHCP Server. -Also you have to install the dhcpd-pools Package. -Under Ubuntu/Debian just run `apt install dhcpd-pools` or under -FreeBSD `pkg install dhcpd-pools`. +Also you have to install the dhcpd-pools and the required Perl +modules. Under Ubuntu/Debian just run `apt install +cpanminus ; cpanm Net::ISC::DHCPd::Leases Mime::Base64 File::Slurp` or under FreeBSD +`pkg install p5-JSON p5-MIME-Base64 p5-App-cpanminus p5-File-Slurp ; cpanm Net::ISC::DHCPd::Leases`. ### SNMP Extend 1. Copy the shell script to the desired host. ``` -wget https://github.com/librenms/librenms-agent/raw/master/snmp/dhcp.py -O /etc/snmp/dhcp.py +wget https://github.com/librenms/librenms-agent/raw/master/snmp/dhcp -O /etc/snmp/dhcp ``` 2. Make the script executable ``` -chmod +x /etc/snmp/dhcp.py +chmod +x /etc/snmp/dhcp ``` -3. Edit your config file, Content of an example /etc/snmp/dhcp.json +3. Edit your snmpd.conf file (usually /etc/snmp/snmpd.conf) and add: ``` -{"leasefile": "/var/lib/dhcp/dhcpd.leases" } +# without using cron +extend dhcpstats /etc/snmp/dhcp -Z +# using cron +extend dhcpstats /bin/cat /var/cache/dhcp_extend ``` -Key 'leasefile' specifies the path to your lease file. -4. Edit your snmpd.conf file (usually /etc/snmp/snmpd.conf) and add: +4. If on a slow system running it via cron may be needed. ``` -extend dhcpstats /etc/snmp/dhcp.py +*/5 * * * * /etc/snmp/dhcp -Z -w /var/cache/dhcp_extend ``` +The following options are also supported. + +| Option | Description | +|------------|---------------------------------| +| `-c $file` | Path to dhcpd.conf. | +| `-l $file` | Path to lease file. | +| `-Z` | Enable GZip+Base64 compression. | +| `-d` | Do not de-dup. | +| `-w $file` | File to write it out to. | + 5. Restart snmpd on your host The application should be auto-discovered as described at the top of diff --git a/includes/html/pages/device/apps/dhcp-stats.inc.php b/includes/html/pages/device/apps/dhcp-stats.inc.php index 493f071a55..5a0a3f13ef 100644 --- a/includes/html/pages/device/apps/dhcp-stats.inc.php +++ b/includes/html/pages/device/apps/dhcp-stats.inc.php @@ -1,15 +1,172 @@ 'Stats', - 'dhcp-stats_pools_percent' => 'Pools Percent', - 'dhcp-stats_pools_current' => 'Pools Current', - 'dhcp-stats_pools_max' => 'Pools Max', - 'dhcp-stats_networks_percent' => 'Networks Percent', - 'dhcp-stats_networks_current' => 'Networks Current', - 'dhcp-stats_networks_max' => 'Networks Max', +use App\Models\Port; + +$link_array = [ + 'page' => 'device', + 'device' => $device['device_id'], + 'tab' => 'apps', + 'app' => 'dhcp-stats', ]; +// app data is only going to exist for this for extend 3+, so don't both displaying it otherwise +if (isset($app->data['pools'])) { + print_optionbar_start(); + echo generate_link('General', $link_array); + echo ' | ' . generate_link('Pools', $link_array, ['app_page' => 'pools']); + echo ' | ' . generate_link('Leases', $link_array, ['app_page' => 'leases']); + print_optionbar_end(); +} + +if (! isset($vars['app_page']) || ! isset($app->data['pools'])) { + $graphs = [ + 'dhcp-stats_stats' => 'Stats', + 'dhcp-stats_pools_percent' => 'Pools Percent', + 'dhcp-stats_pools_current' => 'Pools Current', + 'dhcp-stats_pools_max' => 'Pools Max', + 'dhcp-stats_networks_percent' => 'Networks Percent', + 'dhcp-stats_networks_current' => 'Networks Current', + 'dhcp-stats_networks_max' => 'Networks Max', + ]; +} elseif (isset($vars['app_page']) && $vars['app_page'] == 'pools') { + $pools = $app->data['pools'] ?? []; + print_optionbar_start(); + echo '
Pools
'; + $pool_table = [ + 'headers' => [ + 'CIDR', + 'First IP', + 'Last IP', + 'Max', + 'In Use', + 'Use%', + ], + 'rows' => [], + ]; + foreach ($pools as $key => $pool) { + $pool_table['rows'][$key] = [ + ['data' => $pool['cidr']], + ['data' => $pool['first_ip']], + ['data' => $pool['last_ip']], + ['data' => $pool['max']], + ['data' => $pool['cur']], + ['data' => $pool['percent']], + ]; + } + echo view('widgets/sortable_table', $pool_table); + print_optionbar_end(); + + print_optionbar_start(); + echo '
Subnets Details
'; + print_optionbar_start(); + $pool_detail_table = [ + 'headers' => [ + 'Key', + 'Value', + ], + ]; + foreach ($pools as $pool_key => $pool) { + // re-init the rows the pools detail table + unset($pool_detail_table['rows']); + $pool_detail_table['rows'] = []; + // display it this way as a CIDR may have more than one pool defined for it + // especially true if both IPv4 and IPv6 are in use + echo '
' . $pool['cidr'] . ', ' . $pool['first_ip'] . '-' . $pool['last_ip'] . '
'; + $option_row_int = 0; + // remove these as they are stats and no options related info for the subnet + unset($pool['cur']); + unset($pool['max']); + unset($pool['percent']); + foreach ($pool as $pool_option => $option_value) { + $pool_detail_table['rows'][$option_row_int] = [ + ['data' => $pool_option], + ['data' => $option_value], + ]; + $option_row_int++; + } + echo view('widgets/sortable_table', $pool_detail_table); + } + print_optionbar_end(); + print_optionbar_end(); + + $subnets = $app->data['networks'] ?? []; + print_optionbar_start(); + echo '
Networks
'; + $subnets_table = [ + 'headers' => [ + 'Name', + 'Max', + 'In Use', + 'Use%', + 'Pools', + ], + 'rows' => [], + ]; + foreach ($subnets as $key => $subnet) { + $subnets_table['rows'][$key] = [ + ['data' => $subnet['network']], + ['data' => $subnet['max']], + ['data' => $subnet['cur']], + ['data' => $subnet['percent']], + ['data' => json_encode($subnet['pools'])], + ]; + } + echo view('widgets/sortable_table', $subnets_table); + print_optionbar_end(); +} elseif (isset($vars['app_page']) && $vars['app_page'] == 'leases') { + $leases = $app->data['found_leases'] ?? []; + $table_info = [ + 'headers' => [ + 'IP', + 'State', + 'HW Address', + 'Starts', + 'Ends', + 'Client Hostname', + 'Vendor', + ], + 'rows' => [], + ]; + foreach ($leases as $key => $lease) { + // look and see if we know what that mac belongs to and if so create a link for the device and port + $mac = $lease['hw_address']; + $mac_raw = false; + if (preg_match('/^[A-Ea-e0-9][A-Ea-e0-9]:[A-Ea-e0-9][A-Ea-e0-9]:[A-Ea-e0-9][A-Ea-e0-9]:[A-Ea-e0-9][A-Ea-e0-9]:[A-Ea-e0-9][A-Ea-e0-9]:[A-Ea-e0-9][A-Ea-e0-9]$/', $mac)) { + $port = Port::with('device')->firstWhere(['ifPhysAddress' => str_replace(':', '', $mac)]); + } + if (isset($port)) { + // safe to set given we know we got a valid MAC if a $port is set + $mac_raw = true; + $mac = $mac . ' (' . + generate_device_link(['device_id' => $port->device_id]) . ', ' . + generate_port_link([ + 'label' => $port->label, + 'port_id' => $port->port_id, + 'ifName' => $port->ifName, + 'device_id' => $port->device_id, + ]) . ')'; + } + + if ($lease['client_hostname'] != '') { + $lease['client_hostname'] = base64_decode($lease['client_hostname']); + } + if ($lease['vendor_class_identifier'] != '') { + $lease['vendor_class_identifier'] = base64_decode($lease['vendor_class_identifier']); + } + $table_info['rows'][$key] = [ + ['data' => $lease['ip']], + ['data' => $lease['state']], + ['data' => $mac, 'raw' => $mac_raw], + // display the time as UTC as that keeps things most simple + ['data' => date('Y-m-d\TH:i:s\Z', $lease['starts'])], + ['data' => date('Y-m-d\TH:i:s\Z', $lease['ends'])], + ['data' => $lease['client_hostname']], + ['data' => $lease['vendor_class_identifier']], + ]; + } + echo view('widgets/sortable_table', $table_info); +} + foreach ($graphs as $key => $text) { $graph_type = $key; $graph_array['height'] = '100'; diff --git a/includes/polling/applications/dhcp-stats.inc.php b/includes/polling/applications/dhcp-stats.inc.php index a59d564ba4..0ffdffd0ba 100644 --- a/includes/polling/applications/dhcp-stats.inc.php +++ b/includes/polling/applications/dhcp-stats.inc.php @@ -28,7 +28,7 @@ $version = intval($version); if ($version == 1) { $output = 'LEGACY'; -} elseif ($version == 2) { +} elseif ($version >= 2) { $output = 'OK'; } else { $output = 'UNSUPPORTED'; @@ -38,7 +38,7 @@ $metrics = []; $category = 'stats'; if (intval($version) == 1) { [$dhcp_total, $dhcp_active, $dhcp_expired, $dhcp_released, $dhcp_abandoned, $dhcp_reset, $dhcp_bootp, $dhcp_backup, $dhcp_free] = explode("\n", $dhcpstats); -} elseif ($version == 2) { +} elseif ($version >= 2) { $lease_data = $dhcpstats['leases']; $dhcp_total = $lease_data['total']; @@ -80,7 +80,7 @@ $metrics[$name . '_' . $category] = $fields; $tags = ['name' => $name, 'app_id' => $app->app_id, 'rrd_def' => $rrd_def, 'rrd_name' => $rrd_name]; data_update($device, 'app', $tags, $fields); -if ($version == 2) { +if ($version >= 2) { $category = 'pools'; $pool_data = $dhcpstats['pools']; @@ -142,4 +142,8 @@ if ($version == 1) { $app_state = $dhcpstats['all_networks']['cur'] . '/' . $dhcpstats['all_networks']['max']; } +if ($version >= 3) { + $app->data = $dhcpstats; +} + update_application($app, $output, $metrics, $app_state); diff --git a/resources/views/widgets/sortable_table.blade.php b/resources/views/widgets/sortable_table.blade.php new file mode 100644 index 0000000000..4e9edf1834 --- /dev/null +++ b/resources/views/widgets/sortable_table.blade.php @@ -0,0 +1,79 @@ + + + + @foreach ($headers as $key => $header) + + @endforeach + + @foreach ($rows as $row) + + @foreach ($row as $column) + @if (isset($column['raw']) && $column['raw']) + + @else + + @endif + @endforeach + + @endforeach +
{{ $header }}
{!! $column['data'] !!}{{ $column['data'] }}
+ + diff --git a/tests/data/linux_dhcp-stats-v2.json b/tests/data/linux_dhcp-stats-v2.json new file mode 100644 index 0000000000..9a66c8f43d --- /dev/null +++ b/tests/data/linux_dhcp-stats-v2.json @@ -0,0 +1,124 @@ +{ + "applications": { + "discovery": { + "applications": [ + { + "app_type": "dhcp-stats", + "app_state": "UNKNOWN", + "discovered": 1, + "app_state_prev": null, + "app_status": "", + "app_instance": "", + "data": null, + "deleted_at": null + } + ] + }, + "poller": { + "applications": [ + { + "app_type": "dhcp-stats", + "app_state": "OK", + "discovered": 1, + "app_state_prev": "UNKNOWN", + "app_status": "1/65", + "app_instance": "", + "data": null, + "deleted_at": null + } + ], + "application_metrics": [ + { + "metric": "192.168.14.0_23_networks_current", + "value": 1, + "value_prev": null, + "app_type": "dhcp-stats" + }, + { + "metric": "192.168.14.0_23_networks_max", + "value": 65, + "value_prev": null, + "app_type": "dhcp-stats" + }, + { + "metric": "192.168.14.0_23_networks_percent", + "value": 1.538, + "value_prev": null, + "app_type": "dhcp-stats" + }, + { + "metric": "192.168.15.128_-_192.168.15.192_pools_current", + "value": 1, + "value_prev": null, + "app_type": "dhcp-stats" + }, + { + "metric": "192.168.15.128_-_192.168.15.192_pools_max", + "value": 65, + "value_prev": null, + "app_type": "dhcp-stats" + }, + { + "metric": "192.168.15.128_-_192.168.15.192_pools_percent", + "value": 1.538, + "value_prev": null, + "app_type": "dhcp-stats" + }, + { + "metric": "dhcp-stats_stats_dhcp_abandoned", + "value": 0, + "value_prev": null, + "app_type": "dhcp-stats" + }, + { + "metric": "dhcp-stats_stats_dhcp_active", + "value": 3, + "value_prev": null, + "app_type": "dhcp-stats" + }, + { + "metric": "dhcp-stats_stats_dhcp_backup", + "value": 0, + "value_prev": null, + "app_type": "dhcp-stats" + }, + { + "metric": "dhcp-stats_stats_dhcp_bootp", + "value": 0, + "value_prev": null, + "app_type": "dhcp-stats" + }, + { + "metric": "dhcp-stats_stats_dhcp_expired", + "value": 0, + "value_prev": null, + "app_type": "dhcp-stats" + }, + { + "metric": "dhcp-stats_stats_dhcp_free", + "value": 52, + "value_prev": null, + "app_type": "dhcp-stats" + }, + { + "metric": "dhcp-stats_stats_dhcp_released", + "value": 0, + "value_prev": null, + "app_type": "dhcp-stats" + }, + { + "metric": "dhcp-stats_stats_dhcp_reset", + "value": 0, + "value_prev": null, + "app_type": "dhcp-stats" + }, + { + "metric": "dhcp-stats_stats_dhcp_total", + "value": 52, + "value_prev": null, + "app_type": "dhcp-stats" + } + ] + } + } +} diff --git a/tests/data/linux_dhcp-stats-v3.json b/tests/data/linux_dhcp-stats-v3.json new file mode 100644 index 0000000000..191f742a1d --- /dev/null +++ b/tests/data/linux_dhcp-stats-v3.json @@ -0,0 +1,124 @@ +{ + "applications": { + "discovery": { + "applications": [ + { + "app_type": "dhcp-stats", + "app_state": "UNKNOWN", + "discovered": 1, + "app_state_prev": null, + "app_status": "", + "app_instance": "", + "data": null, + "deleted_at": null + } + ] + }, + "poller": { + "applications": [ + { + "app_type": "dhcp-stats", + "app_state": "OK", + "discovered": 1, + "app_state_prev": "UNKNOWN", + "app_status": "1/65", + "app_instance": "", + "data": "{\"all_networks\":{\"cur\":\"1\",\"max\":\"65\",\"percent\":\"1.538\"},\"leases\":{\"abandoned\":0,\"active\":3,\"backup\":0,\"bootp\":0,\"expired\":0,\"free\":52,\"released\":0,\"reset\":0,\"total\":52},\"networks\":[{\"cur\":\"1\",\"max\":\"65\",\"network\":\"192.168.14.0\\\/23\",\"percent\":\"1.538\"}],\"pools\":[{\"cur\":\"1\",\"first_ip\":\"192.168.15.128\",\"last_ip\":\"192.168.15.192\",\"max\":\"65\",\"percent\":\"1.538\"}]}", + "deleted_at": null + } + ], + "application_metrics": [ + { + "metric": "192.168.14.0_23_networks_current", + "value": 1, + "value_prev": null, + "app_type": "dhcp-stats" + }, + { + "metric": "192.168.14.0_23_networks_max", + "value": 65, + "value_prev": null, + "app_type": "dhcp-stats" + }, + { + "metric": "192.168.14.0_23_networks_percent", + "value": 1.538, + "value_prev": null, + "app_type": "dhcp-stats" + }, + { + "metric": "192.168.15.128_-_192.168.15.192_pools_current", + "value": 1, + "value_prev": null, + "app_type": "dhcp-stats" + }, + { + "metric": "192.168.15.128_-_192.168.15.192_pools_max", + "value": 65, + "value_prev": null, + "app_type": "dhcp-stats" + }, + { + "metric": "192.168.15.128_-_192.168.15.192_pools_percent", + "value": 1.538, + "value_prev": null, + "app_type": "dhcp-stats" + }, + { + "metric": "dhcp-stats_stats_dhcp_abandoned", + "value": 0, + "value_prev": null, + "app_type": "dhcp-stats" + }, + { + "metric": "dhcp-stats_stats_dhcp_active", + "value": 3, + "value_prev": null, + "app_type": "dhcp-stats" + }, + { + "metric": "dhcp-stats_stats_dhcp_backup", + "value": 0, + "value_prev": null, + "app_type": "dhcp-stats" + }, + { + "metric": "dhcp-stats_stats_dhcp_bootp", + "value": 0, + "value_prev": null, + "app_type": "dhcp-stats" + }, + { + "metric": "dhcp-stats_stats_dhcp_expired", + "value": 0, + "value_prev": null, + "app_type": "dhcp-stats" + }, + { + "metric": "dhcp-stats_stats_dhcp_free", + "value": 52, + "value_prev": null, + "app_type": "dhcp-stats" + }, + { + "metric": "dhcp-stats_stats_dhcp_released", + "value": 0, + "value_prev": null, + "app_type": "dhcp-stats" + }, + { + "metric": "dhcp-stats_stats_dhcp_reset", + "value": 0, + "value_prev": null, + "app_type": "dhcp-stats" + }, + { + "metric": "dhcp-stats_stats_dhcp_total", + "value": 52, + "value_prev": null, + "app_type": "dhcp-stats" + } + ] + } + } +} diff --git a/tests/snmpsim/linux_dhcp-stats-v2.snmprec b/tests/snmpsim/linux_dhcp-stats-v2.snmprec new file mode 100644 index 0000000000..3b14543659 --- /dev/null +++ b/tests/snmpsim/linux_dhcp-stats-v2.snmprec @@ -0,0 +1,10 @@ +1.3.6.1.2.1.1.1.0|4|Linux server 3.10.0-693.5.2.el7.x86_64 #1 SMP Fri Oct 20 20:32:50 UTC 2017 x86_64 +1.3.6.1.2.1.1.2.0|6|1.3.6.1.4.1.8072.3.2.10 +1.3.6.1.2.1.1.3.0|67|77550514 +1.3.6.1.2.1.1.4.0|4| +1.3.6.1.2.1.1.5.0|4| +1.3.6.1.2.1.1.6.0|4| +1.3.6.1.2.1.25.1.1.0|67|77552962 +1.3.6.1.4.1.8072.1.3.2.2.1.21.6.100.105.115.116.114.111|2|1 +1.3.6.1.4.1.8072.1.3.2.2.1.21.9.100.104.99.112.115.116.97.116.115|2|1 +1.3.6.1.4.1.8072.1.3.2.3.1.2.9.100.104.99.112.115.116.97.116.115|4x|7b0a20202264617461223a207b0a2020202022616c6c5f6e6574776f726b73223a207b0a20202020202022637572223a202231222c0a202020202020226d6178223a20223635222c0a2020202020202270657263656e74223a2022312e353338220a202020207d2c0a20202020226c6561736573223a207b0a202020202020226162616e646f6e6564223a20302c0a20202020202022616374697665223a20332c0a202020202020226261636b7570223a20302c0a20202020202022626f6f7470223a20302c0a2020202020202265787069726564223a20302c0a2020202020202266726565223a2035322c0a2020202020202272656c6561736564223a20302c0a202020202020227265736574223a20302c0a20202020202022746f74616c223a2035320a202020207d2c0a20202020226e6574776f726b73223a205b0a2020202020207b0a202020202020202022637572223a202231222c0a2020202020202020226d6178223a20223635222c0a2020202020202020226e6574776f726b223a20223139322e3136382e31342e302f3233222c0a20202020202020202270657263656e74223a2022312e353338220a2020202020207d0a202020205d2c0a2020202022706f6f6c73223a205b0a2020202020207b0a202020202020202022637572223a202231222c0a20202020202020202266697273745f6970223a20223139322e3136382e31352e313238222c0a2020202020202020226c6173745f6970223a20223139322e3136382e31352e313932222c0a2020202020202020226d6178223a20223635222c0a20202020202020202270657263656e74223a2022312e353338220a2020202020207d0a202020205d0a20207d2c0a2020226572726f72223a20302c0a2020226572726f72537472696e67223a2022222c0a20202276657273696f6e223a20320a7d0a diff --git a/tests/snmpsim/linux_dhcp-stats-v3.snmprec b/tests/snmpsim/linux_dhcp-stats-v3.snmprec new file mode 100644 index 0000000000..bf37712b4f --- /dev/null +++ b/tests/snmpsim/linux_dhcp-stats-v3.snmprec @@ -0,0 +1,10 @@ +1.3.6.1.2.1.1.1.0|4|Linux server 3.10.0-693.5.2.el7.x86_64 #1 SMP Fri Oct 20 20:32:50 UTC 2017 x86_64 +1.3.6.1.2.1.1.2.0|6|1.3.6.1.4.1.8072.3.2.10 +1.3.6.1.2.1.1.3.0|67|77550514 +1.3.6.1.2.1.1.4.0|4| +1.3.6.1.2.1.1.5.0|4| +1.3.6.1.2.1.1.6.0|4| +1.3.6.1.2.1.25.1.1.0|67|77552962 +1.3.6.1.4.1.8072.1.3.2.2.1.21.6.100.105.115.116.114.111|2|1 +1.3.6.1.4.1.8072.1.3.2.2.1.21.9.100.104.99.112.115.116.97.116.115|2|1 +1.3.6.1.4.1.8072.1.3.2.3.1.2.9.100.104.99.112.115.116.97.116.115|4x|7b0a20202264617461223a207b0a2020202022616c6c5f6e6574776f726b73223a207b0a20202020202022637572223a202231222c0a202020202020226d6178223a20223635222c0a2020202020202270657263656e74223a2022312e353338220a202020207d2c0a20202020226c6561736573223a207b0a202020202020226162616e646f6e6564223a20302c0a20202020202022616374697665223a20332c0a202020202020226261636b7570223a20302c0a20202020202022626f6f7470223a20302c0a2020202020202265787069726564223a20302c0a2020202020202266726565223a2035322c0a2020202020202272656c6561736564223a20302c0a202020202020227265736574223a20302c0a20202020202022746f74616c223a2035320a202020207d2c0a20202020226e6574776f726b73223a205b0a2020202020207b0a202020202020202022637572223a202231222c0a2020202020202020226d6178223a20223635222c0a2020202020202020226e6574776f726b223a20223139322e3136382e31342e302f3233222c0a20202020202020202270657263656e74223a2022312e353338220a2020202020207d0a202020205d2c0a2020202022706f6f6c73223a205b0a2020202020207b0a202020202020202022637572223a202231222c0a20202020202020202266697273745f6970223a20223139322e3136382e31352e313238222c0a2020202020202020226c6173745f6970223a20223139322e3136382e31352e313932222c0a2020202020202020226d6178223a20223635222c0a20202020202020202270657263656e74223a2022312e353338220a2020202020207d0a202020205d0a20207d2c0a2020226572726f72223a20302c0a2020226572726f72537472696e67223a2022222c0a20202276657273696f6e223a20330a7d0a