You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
262 lines
9.4 KiB
262 lines
9.4 KiB
#!/usr/bin/perl -w
|
|
|
|
use strict;
|
|
use warnings;
|
|
use JSON;
|
|
use Getopt::Long;
|
|
use LWP::UserAgent;
|
|
use HTTP::Cookies;
|
|
use URI;
|
|
use Data::Dumper;
|
|
|
|
umask 077;
|
|
|
|
my $user = 'zabbix';
|
|
my $pass = 'secret';
|
|
my $site = 'default';
|
|
my $url = 'https://localhost:8443';
|
|
my $certcheck = 1;
|
|
my $unifi;
|
|
my $dev;
|
|
my $station;
|
|
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,
|
|
'cert-check!' => \$certcheck,
|
|
'unifi' => \$unifi,
|
|
'dev=s' => \$dev,
|
|
'station=s' => \$station,
|
|
'net=s' => \$net,
|
|
'wlan=s' => \$wlan,
|
|
'pretty' => \$pretty
|
|
);
|
|
|
|
# If connecting to localhost, no need to check certificate
|
|
my $uri = URI->new($url);
|
|
if ($uri->host =~ m/^localhost|127\.0\.0/){
|
|
$certcheck = 0;
|
|
}
|
|
|
|
my @radio_proto = qw/a b g na ng ac/;
|
|
my $resp;
|
|
my $username = $ENV{LOGNAME} || $ENV{USER} || getpwuid($<);
|
|
my $cj = HTTP::Cookies->new(
|
|
file => "/tmp/.unifi_$username.txt",
|
|
autosave => 1,
|
|
ignore_discard => 1
|
|
);
|
|
|
|
my $sslopts = {};
|
|
if (not $certcheck){
|
|
$sslopts = { verify_hostname => 0, SSL_verify_mode => 0 }
|
|
}
|
|
my $ua = LWP::UserAgent->new(
|
|
ssl_opts => $sslopts,
|
|
cookie_jar => $cj
|
|
);
|
|
|
|
# Check if we need to login
|
|
$resp = $ua->get($url . '/api/self/sites');
|
|
if ($resp->is_error){
|
|
# Log into 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;
|
|
$resp = $ua->get($url . '/api/self/sites');
|
|
die $resp->message . "\n" if $resp->is_error;
|
|
}
|
|
|
|
# Now, we need to get the site ID
|
|
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 "ZBX_NOTSUPPORTED\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 (defined $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 "ZBX_NOTSUPPORTED\n" if $resp->is_error;
|
|
$json->{$_} = from_json($resp->decoded_content)->{data}->[0]->{$_}
|
|
foreach (qw/version build update_available/);
|
|
|
|
# Get unarchived alarms
|
|
$resp = $ua->post($url . '/api/s/' . $site . '/stat/alarm',
|
|
Content => to_json({ archived => 'false' }),
|
|
Content_Type => 'application/json;charset=UTF-8'
|
|
);
|
|
die "ZBX_NOTSUPPORTED\n" if $resp->is_error;
|
|
$json->{alarm} = scalar @{from_json($resp->decoded_content)->{data}};
|
|
|
|
} elsif ($dev) {
|
|
# Dev is identified by MAC
|
|
$resp = $ua->get($url . '/api/s/' . $site . '/stat/device/' . $dev);
|
|
die "ZBX_NOTSUPPORTED\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 (defined $obj->{$_});
|
|
}
|
|
# 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/);
|
|
|
|
# If this is an ap
|
|
if ($obj->{type} eq 'uap'){
|
|
|
|
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}->{ap}->{$_} if (defined $obj->{stat}->{ap}->{$_});
|
|
}
|
|
|
|
# Count the number of SSID served
|
|
$json->{num_wlan} = scalar @{$obj->{radio_table}};
|
|
|
|
$resp = $ua->get($url . '/api/s/' . $site . '/stat/sta');
|
|
die "ZBX_NOTSUPPORTED\n" if $resp->is_error;
|
|
|
|
foreach my $proto (@radio_proto){
|
|
$json->{$_ . $proto} = 0 foreach (qw/num_sta_ avg_rx_rate_ avg_tx_rate_/);
|
|
}
|
|
|
|
foreach my $entry (@{from_json($resp->decoded_content)->{data}}){
|
|
next if (not $entry->{ap_mac} or $entry->{ap_mac} ne $dev or $entry->{is_wired} == JSON::PP::true);
|
|
foreach (@radio_proto){
|
|
if ($entry->{radio_proto} eq $_){
|
|
$json->{'num_sta_' . $_}++;
|
|
$json->{'avg_rx_rate_' . $_} += $entry->{rx_rate};
|
|
$json->{'avg_tx_rate_' . $_} += $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) ? undef : $json->{'avg_' . $_} / $json->{num_sta}
|
|
foreach (qw/satisfaction tx_power signal noise/);
|
|
foreach my $proto (@radio_proto){
|
|
$json->{'avg_' . $_ . '_rate_' . $proto} = ($json->{'num_sta_' . $proto} == 0) ?
|
|
undef : $json->{'avg_' . $_ . '_rate_' . $proto} / $json->{'num_sta_' . $proto}
|
|
foreach (qw/tx rx/);
|
|
}
|
|
} elsif ($obj->{type} eq 'usw'){
|
|
foreach (qw/rx_packets tx_packets
|
|
rx_bytes tx_bytes rx_errors tx_errors
|
|
rx_dropped tx_dropped/){
|
|
$json->{net_stats}->{$_} = $obj->{stat}->{sw}->{$_} if (defined $obj->{stat}->{sw}->{$_});
|
|
}
|
|
}
|
|
} elsif ($station) {
|
|
# Client is identified by MAC
|
|
$resp = $ua->get($url . '/api/s/' . $site . '/stat/sta/' . $station);
|
|
die "ZBX_NOTSUPPORTED\n" if $resp->is_error;
|
|
my $obj = from_json($resp->decoded_content)->{data}->[0];
|
|
my @client_base = qw/rx_packets tx_packets rx_bytes tx_bytes hostname last_seen ip authorized oui is_guest/;
|
|
foreach (@client_base){
|
|
$json->{$_} = $obj->{$_} || 0;
|
|
}
|
|
# Convert last_seen to relative
|
|
$json->{last_seen} = time - $json->{last_seen};
|
|
|
|
# For wireless stations, we gather some more info
|
|
if ($obj->{is_wired} == JSON::PP::false){
|
|
my @client_wireless = qw/rx_rate tx_rate essid ap_mac tx_power radio_proto signal noise satisfaction/;
|
|
foreach (@client_wireless){
|
|
$json->{$_} = $obj->{$_} || 0;
|
|
}
|
|
# We have the MAC of the AP, lets try to find the name of this AP
|
|
$resp = $ua->get($url . '/api/s/' . $site . '/stat/device/' . $json->{ap_mac});
|
|
die "ZBX_NOTSUPPORTED\n" if $resp->is_error;
|
|
$json->{ap} = from_json($resp->decoded_content)->{data}->[0]->{name};
|
|
}
|
|
|
|
} elsif ($wlan) {
|
|
# Wlan is identified by ID
|
|
$resp = $ua->get($url . '/api/s/' . $site . '/rest/wlanconf/' . $wlan);
|
|
die "ZBX_NOTSUPPORTED\n" if $resp->is_error;
|
|
my $obj = from_json($resp->decoded_content)->{data}->[0];
|
|
foreach (qw/name security mac_filter_policy vlan/){
|
|
$json->{$_} = $obj->{$_};
|
|
}
|
|
# For boolean, we need to convert
|
|
foreach (qw/enabled is_guest mac_filter_enabled vlan_enabled/){
|
|
$json->{$_} = (defined $obj->{$_} and $obj->{$_} == JSON::PP::true) ? 1 : 0;
|
|
}
|
|
|
|
# Now, we need to count stations for each SSID
|
|
$resp = $ua->get($url . '/api/s/' . $site . '/stat/sta');
|
|
die "ZBX_NOTSUPPORTED\n" if $resp->is_error;
|
|
|
|
# Set default values to 0
|
|
$json->{num_sta} = 0;
|
|
$json->{'num_sta_' . $_} = 0 foreach (@radio_proto);
|
|
$json->{$_} = 0 foreach (qw/rx_bytes tx_bytes rx_packets tx_packets/);
|
|
|
|
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}++;
|
|
foreach (@radio_proto){
|
|
if ($entry->{radio_proto} eq $_){
|
|
$json->{'num_sta_' . $_}++;
|
|
$json->{'avg_rx_rate_' . $_} += $entry->{rx_rate};
|
|
$json->{'avg_tx_rate_' . $_} += $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) ? undef : $json->{'avg_' . $_} / $json->{num_sta}
|
|
foreach (qw/satisfaction tx_power signal noise/);
|
|
foreach my $proto (@radio_proto){
|
|
$json->{'avg_' . $_ . '_rate_' . $proto} = ($json->{'num_sta_' . $proto} == 0) ?
|
|
undef : $json->{'avg_' . $_ . '_rate_' . $proto} / $json->{'num_sta_' . $proto}
|
|
foreach (qw/tx rx/);
|
|
}
|
|
}
|
|
|
|
print to_json($json, { pretty => $pretty });
|
|
|