From 30b6fdad556815cc94e26df58a8eb09732e38cab Mon Sep 17 00:00:00 2001 From: Daniel Berteaud Date: Thu, 13 Sep 2018 16:29:15 +0200 Subject: [PATCH] Add scripts to monitor Unifi sites --- zabbix_conf/unifi.conf | 2 + zabbix_scripts/check_unifi | 155 +++++++++++++++++++++++++++++++++++++++++++++ zabbix_scripts/disco_unifi | 112 ++++++++++++++++++++++++++++++++ 3 files changed, 269 insertions(+) create mode 100644 zabbix_conf/unifi.conf create mode 100755 zabbix_scripts/check_unifi create mode 100755 zabbix_scripts/disco_unifi diff --git a/zabbix_conf/unifi.conf b/zabbix_conf/unifi.conf new file mode 100644 index 0000000..0a8dae9 --- /dev/null +++ b/zabbix_conf/unifi.conf @@ -0,0 +1,2 @@ +UserParameter=unifi.discovery[*],/var/lib/zabbix/bin/disco_unifi --url=$1 --user=$2 --pass=$3 --site=$4 --what=$5 --type=$6 +UserParameter=unifi.check.all[*],/var/lib/zabbix/bin/check_unifi --url=$1 --user=$2 --pass=$3 --site=$4 --$5 $6 diff --git a/zabbix_scripts/check_unifi b/zabbix_scripts/check_unifi new file mode 100755 index 0000000..b8d034d --- /dev/null +++ b/zabbix_scripts/check_unifi @@ -0,0 +1,155 @@ +#!/usr/bin/perl -w + +use strict; +use warnings; +use JSON; +use Getopt::Long; +use LWP::UserAgent; +use Data::Dumper; + +my $user = 'zabbix'; +my $pass = 'secret'; +my $site = 'default'; +my $url = 'https://localhost:8443'; +my $unifi; +my $dev; +my $client; +my $net; +my $wlan; +my $pretty = 0; + +my $json = {}; +my $site_id; + +GetOptions ( + 'user=s' => \$user, + 'password|p=s' => \$pass, + 'site=s' => \$site, + 'url=s' => \$url, + 'unifi' => \$unifi, + 'dev=s' => \$dev, + 'client=s' => \$client, + 'net=s' => \$net, + 'wlan=s' => \$wlan, + 'pretty' => \$pretty +); + +# Log into the API +my $resp; +my $ua = LWP::UserAgent->new( + ssl_opts => { verify_hostname => 0 }, + cookie_jar => {} +); +$resp = $ua->post( + $url . '/api/login', + Content => to_json({ username => $user, password => $pass }), + Content_Type => 'application/json;charset=UTF-8' +); +die "Login failed: " . $resp->message . "\n" if $resp->is_error; + +# Now, we need to get the site ID +$resp = $ua->get($url . '/api/self/sites'); +die $resp->message . "\n" if $resp->is_error; +foreach (@{from_json($resp->decoded_content)->{data}}){ + if ($_->{name} eq $site || $_->{desc} eq $site){ + $site_id = $_->{_id}; + # If site is referenced by description, translate it to name + $site = $_->{name} if ($_->{name} ne $site); + last; + } +} +die "Site $site not found\n" unless ($site_id); + +# Global info about the instance of Unifi +if ($unifi){ + $resp = $ua->get($url . '/api/s/' . $site . '/stat/health'); + die $resp->message . "\n" if $resp->is_error; + foreach my $entry (@{from_json($resp->decoded_content)->{data}}){ + if ($entry->{subsystem} eq 'wlan'){ + $json->{wireless_clients} = $entry->{num_user}; + $json->{wireless_guests} = $entry->{num_guest}; + } elsif ($entry->{subsystem} eq 'lan'){ + $json->{wired_clients} = $entry->{num_user}; + $json->{wired_guests} = $entry->{num_guest}; + } + foreach (qw/adopted pending disabled/){ + $json->{'dev_' . $_} += $entry->{'num_' . $_} if ($entry->{'num_' . $_}); + } + foreach (qw/num_ap num_sw num_gw/){ + $json->{$_} += $entry->{$_} if ($entry->{$_}); + } + } + $json->{$_} ||= 0 foreach (qw/wireless_clients wireless_guests + wired_clients wired_guests dev_adopted + dev_pending dev_disabled num_ap num_sw + num_gw/); + $resp = $ua->get($url . '/api/s/' . $site . '/stat/sysinfo'); + die $resp->message . "\n" if $resp->is_error; + $json->{$_} = from_json($resp->decoded_content)->{data}->[0]->{$_} + foreach (qw/version build update_available/); + +} elsif ($dev) { + $resp = $ua->get($url . '/api/s/' . $site . '/stat/device/' . $dev); + die $resp->message . "\n" if $resp->is_error; + my $obj = from_json($resp->decoded_content)->{data}->[0]; + foreach (qw/sys_stats locating serial name num_sta user-num_sta + guest-num_sta inform_url version model state type + cfgversion adopted avg_client_signal/){ + $json->{$_} = $obj->{$_} if ($obj->{$_}); + } + foreach (qw/guest-rx_packets guest-tx_packets guest-rx_bytes + guest-tx_bytes user-rx_packets user-tx_packets + user-rx_bytes user-tx_bytes rx_packets tx_packets + rx_bytes tx_bytes rx_errors tx_errors + rx_dropped tx_dropped/){ + $json->{net_stats}->{$_} = $obj->{stat}->{$_} if ($obj->{stat}->{$_}); + } + # Convert last seen into a relative time + $json->{last_seen} = time - $obj->{last_seen}; + # Add some more info in sys_stats + $json->{sys_stats}->{$_} = $obj->{'system-stats'}->{$_} foreach (qw/cpu mem uptime/); + # Count the number of SSID served + $json->{num_wlan} = scalar @{$obj->{radio_table}} if ($obj->{radio_table}); +} elsif ($client) { + +} elsif ($wlan) { + $resp = $ua->get($url . '/api/s/' . $site . '/rest/wlanconf/' . $wlan); + die $resp->message . "\n" if $resp->is_error; + my $obj = from_json($resp->decoded_content)->{data}->[0]; + foreach (qw/name enabled is_guest mac_filter_enabled security + mac_filter_policy vlan vlan_enabled/){ + $json->{$_} = $obj->{$_}; + } + + # Now, we need to count stations for each SSID + $resp = $ua->get($url . '/api/s/' . $site . '/stat/sta'); + die $resp->message . "\n" if $resp->is_error; + $json->{$_} = 0 foreach (qw/avg_rx_rate_ac avg_tx_rate_ac avg_rx_rate_ng + avg_tx_rate_ng num_sta num_sta_ac num_sta_ng/); + foreach my $entry (@{from_json($resp->decoded_content)->{data}}){ + next if (not $entry->{essid} or $entry->{essid} ne $json->{name} or $entry->{is_wired} == JSON::PP::true); + $json->{num_sta}++; + if ($entry->{radio_proto} eq 'ac'){ + $json->{num_sta_ac}++; + $json->{avg_rx_rate_ac} += $entry->{rx_rate}; + $json->{avg_tx_rate_ac} += $entry->{tx_rate}; + } elsif ($entry->{radio_proto} eq 'ng'){ + $json->{num_sta_ng}++; + $json->{avg_rx_rate_ng} += $entry->{rx_rate}; + $json->{avg_tx_rate_ng} += $entry->{tx_rate}; + } + $json->{$_} += $entry->{$_} foreach (qw/rx_bytes tx_bytes rx_packets tx_packets/); + $json->{'avg_' . $_} += $entry->{$_} foreach (qw/satisfaction tx_power signal noise/); + } + # Now lets compute average values + $json->{'avg_' . $_} = ($json->{num_sta} == 0) ? 0 : $json->{'avg_' . $_} / $json->{num_sta} + foreach (qw/satisfaction tx_power signal noise/); + $json->{$_} = ($json->{num_sta_ac} == 0) ? 0 : $json->{$_} / $json->{num_sta_ac} + foreach (qw/avg_rx_rate_ac avg_tx_rate_ac/); + $json->{$_} = ($json->{num_sta_ng} == 0) ? 0 : $json->{$_} / $json->{num_sta_ng} + foreach (qw/avg_rx_rate_ng avg_tx_rate_ng/); +} + +# TODO: convert JSON bool to 0/1 + +print to_json($json, { pretty => $pretty }); diff --git a/zabbix_scripts/disco_unifi b/zabbix_scripts/disco_unifi new file mode 100755 index 0000000..bf8545f --- /dev/null +++ b/zabbix_scripts/disco_unifi @@ -0,0 +1,112 @@ +#!/usr/bin/perl -w + +use strict; +use warnings; +use JSON; +use Getopt::Long; +use LWP::UserAgent; +use Data::Dumper; + +my $user = 'zabbix'; +my $pass = 'secret'; +my $site = 'default'; +my $url = 'https://localhost:8443'; +my $what = 'devices'; +my $type = 'all'; +my $pretty = 0; + +my $json = {}; +@{$json->{data}} = (); + +GetOptions ( + 'user=s' => \$user, + 'password|p=s' => \$pass, + 'site=s' => \$site, + 'url=s' => \$url, + 'what=s' => \$what, + 'type:s' => \$type, + 'pretty' => \$pretty +); + +# An empty type is the same as all +$type = 'all' if ($type eq ''); +my $site_id; +my $resp; +my $ua = LWP::UserAgent->new( + ssl_opts => { verify_hostname => 0 }, + cookie_jar => {} +); + +# Login on the API +$resp = $ua->post( + $url . '/api/login', + Content => to_json({ username => $user, password => $pass }), + Content_Type => 'application/json;charset=UTF-8' +); +die "Login failed: " . $resp->message . "\n" if $resp->is_error; +# Now, we need to get the site ID +$resp = $ua->get($url . '/api/self/sites'); +die $resp->message . "\n" if $resp->is_error; +foreach (@{from_json($resp->decoded_content)->{data}}){ + if ($_->{name} eq $site || $_->{desc} eq $site){ + $site_id = $_->{_id}; + # If site is referenced by description, translate it to name + $site = $_->{name} if ($_->{name} ne $site); + last; + } +} +die "Site $site not found\n" unless ($site_id); + +if ($what eq 'devices'){ + $resp = $ua->get($url . '/api/s/' . $site . '/stat/device'); + die $resp->message . "\n" if $resp->is_error; + foreach my $entry (@{from_json($resp->decoded_content)->{data}}){ + next if ($type ne 'all' && $entry->{type} ne $type); + push @{$json->{data}}, { + '{#UNIFI_DEV_ID}' => $entry->{device_id}, + '{#UNIFI_DEV_ADOPTED}' => $entry->{adopted}, + '{#UNIFI_DEV_MODEL}' => $entry->{model}, + '{#UNIFI_DEV_NAME}' => $entry->{name}, + '{#UNIFI_DEV_MAC}' => $entry->{mac}, + '{#UNIFI_DEV_TYPE}' => $entry->{type} + }; + } +} elsif ($what eq 'clients'){ + $resp = $ua->get($url . '/api/s/' . $site . '/stat/alluser'); + die $resp->message . "\n" if $resp->is_error; + foreach my $entry (@{from_json($resp->decoded_content)->{data}}){ + # Ignore other sites + next if ($entry->{site_id} ne $site_id); + # Ignore clients not seen since more than 30 days + next if (time - $entry->{last_seen} > 2592000); + next if ($type eq 'wireless' and $entry->{is_wired} eq 'true'); + next if ($type eq 'wired' and $entry->{is_wired} eq 'false'); + push @{$json->{data}}, { + '{#UNIFI_STA_ID}' => $entry->{_id}, + '{#UNIFI_STA_NAME}' => (defined $entry->{hostname}) ? $entry->{hostname} : $entry->{mac} + }; + } +} elsif ($what eq 'networks'){ + $resp = $ua->get($url . '/api/s/' . $site . '/rest/networkconf'); + die $resp->message . "\n" if $resp->is_error; + foreach my $entry (@{from_json($resp->decoded_content)->{data}}){ + # Ignore other sites + next if ($entry->{site_id} ne $site_id); + next if ($type ne 'all' and $entry->{purpose} ne $type); + push @{$json->{data}}, { + '{#UNIFI_NET_ID}' => $entry->{_id}, + '{#UNIFI_NET_NAME}' => $entry->{name} + }; + } +} elsif ($what eq 'wlan') { + $resp = $ua->get($url . '/api/s/' . $site . '/rest/wlanconf'); + die $resp->message . "\n" if $resp->is_error; + foreach my $entry (@{from_json($resp->decoded_content)->{data}}){ + push @{$json->{data}}, { + '{#UNIFI_WLAN_ID}' => $entry->{_id}, + '{#UNIFI_WLAN_NAME}' => $entry->{name} + }; + } +} + +print to_json($json, { pretty => $pretty });