freeradius-snmp.pl [plain text]
use strict;
use warnings;
use threads;
use threads::shared;
use Net::Radius::Packet;
use Net::Radius::Dictionary;
use NetSNMP::agent qw/:all/;
use NetSNMP::ASN qw/:all/;
use IO::Socket::INET;
use Digest::HMAC_MD5;
use Log::Log4perl qw/:easy/;
my $cfg = {
snmp => {
agent => {
Name => 'freeradius-snmp',
AgentX => 1,
},
oid_root => '1.3.6.1.2.1.67',
oid_sub => {
1 => [qw/auth proxyauth/],
2 => [qw/acct proxyacct/],
},
},
radius => {
host => 'localhost',
port => 18120,
secret => 'adminsecret',
dictionary => 'dictionary.hacked',
refresh_rate => 20,
},
log => {
level => $DEBUG,
layout => '%d{ISO8601} <%p> (%L) %m%n',
file => 'STDERR'
},
clients => undef,
};
Log::Log4perl->easy_init($cfg->{log});
INFO 'starting';
my %snmp_data :shared;
my @snmp_data_k :shared;
INFO 'initializing snmp';
my $agent = new NetSNMP::agent(%{$cfg->{snmp}->{agent}});
INFO 'launching radius client thread';
threads->create(\&radius_updater);
$agent->register(
$cfg->{snmp}->{agent}->{Name},
$cfg->{snmp}->{oid_root}.'.'.$_, \&snmp_handler) or die
foreach keys %{$cfg->{snmp}->{oid_sub}};
INFO 'entering client main loop';
$agent->main_loop;
WARN 'something caused me to exit';
exit 0;
sub radius_stats_init {
our ( $d, $s, $rid );
$d = new Net::Radius::Dictionary;
$d->readfile($cfg->{radius}->{dictionary});
srand ($$ ^ time);
$rid = int rand 255;
$s = new IO::Socket::INET(
PeerHost => $cfg->{radius}->{host},
PeerPort => $cfg->{radius}->{port},
Proto => 'udp',
Timeout => 5) or die;
}
sub radius_stats_get {
my ( $type, %args ) = @_;
our ( $d, $s, $rid );
my $p_req = new Net::Radius::Packet $d;
$p_req->set_code('Status-Server');
$p_req->set_vsattr('FreeRADIUS', 'FreeRADIUS-Statistics-Type', $type);
$p_req->set_vsattr('FreeRADIUS', $_, $args{$_}) foreach keys %args;
$p_req->set_identifier($rid++);
$p_req->set_authenticator(pack 'C*', map { int rand 255 } 0..15);
$p_req->set_attr('Message-Authenticator', "\0"x16, 1);
$p_req->set_attr('Message-Authenticator', Digest::HMAC_MD5::hmac_md5($p_req->pack, $cfg->{radius}->{secret}), 1);
$s->send($p_req->pack) or die;
my $p_data;
if ( defined $s->recv($p_data, 2048) ) {
my $p_res = new Net::Radius::Packet $d, $p_data;
my %response = map {
$_ => $p_res->vsattr($d->vendor_num('FreeRADIUS'), $_)->[0]
} $p_res->vsattributes($d->vendor_num('FreeRADIUS'));
return \%response;
}else {
warn "no answer, $!\n";
return undef;
}
}
sub radius_stats_get_global { return radius_stats_get(0x1f); }
sub radius_stats_get_client { return radius_stats_get(0x3f, 'FreeRADIUS-Stats-Client-Number' => $_[0]); }
sub radius_updater {
radius_stats_init();
while (1) {
INFO 'fetching new data';
my $main_stat = radius_stats_get_global();
if ( defined $main_stat ) {
my @clients_stat = ();
if ( $cfg->{clients} ) {
my $client_id = 0;
while (1) {
my $client_stat = radius_stats_get_client($client_id);
last unless exists $client_stat->{'FreeRADIUS-Stats-Client-IP-Address'};
push @clients_stat, $client_stat;
$client_id += 1;
}
}
INFO 'got data, updating stats';
radius_snmp_stats($main_stat, \@clients_stat);
}else {
WARN 'problem with fetching data';
}
INFO 'stats updated, sleeping';
sleep $cfg->{radius}->{refresh_rate};
}
}
sub oid_s { return join '.', $_[0]->to_array; }
sub snmp_handler {
DEBUG 'got new request';
my ($handler, $registration_info, $request_info, $requests) = @_;
lock %snmp_data;
lock @snmp_data_k;
for ( my $request = $requests; $request; $request = $request->next() ) {
INFO 'request type '.$request_info->getMode.' for oid: '.oid_s($request->getOID);
if ( $request_info->getMode == MODE_GET ) {
my $oid_s = oid_s($request->getOID);
if ( exists $snmp_data{$oid_s} ) {
$request->setValue($snmp_data{$oid_s}->[0], ''.$snmp_data{$oid_s}->[1]);
}
}elsif ( $request_info->getMode == MODE_GETNEXT ) {
foreach my $oid ( @snmp_data_k ) {
if ( $request->getOID < NetSNMP::OID->new($oid) ) {
$request->setValue($snmp_data{$oid}->[0], ''.$snmp_data{$oid}->[1]);
$request->setOID($oid);
last;
}
}
}else {
$request->setError($request_info, SNMP_ERR_READONLY);
}
}
DEBUG 'finished processing the request';
}
sub radius_snmp_stats_init_auth {
my ( $snmp_data_n, $oid, $clients ) = @_;
@{$snmp_data_n->{$oid.'.1.1.1.1'} = &share([])} = (ASN_OCTET_STR, ''); @{$snmp_data_n->{$oid.'.1.1.1.2'} = &share([])} = (ASN_TIMETICKS, 0); @{$snmp_data_n->{$oid.'.1.1.1.3'} = &share([])} = (ASN_TIMETICKS, 0); @{$snmp_data_n->{$oid.'.1.1.1.4'} = &share([])} = (ASN_INTEGER, 0); @{$snmp_data_n->{$oid.'.1.1.1.5'} = &share([])} = (ASN_COUNTER, 0); @{$snmp_data_n->{$oid.'.1.1.1.6'} = &share([])} = (ASN_COUNTER, 0); @{$snmp_data_n->{$oid.'.1.1.1.7'} = &share([])} = (ASN_COUNTER, 0); @{$snmp_data_n->{$oid.'.1.1.1.8'} = &share([])} = (ASN_COUNTER, 0); @{$snmp_data_n->{$oid.'.1.1.1.9'} = &share([])} = (ASN_COUNTER, 0); @{$snmp_data_n->{$oid.'.1.1.1.10'} = &share([])} = (ASN_COUNTER, 0); @{$snmp_data_n->{$oid.'.1.1.1.11'} = &share([])} = (ASN_COUNTER, 0); @{$snmp_data_n->{$oid.'.1.1.1.12'} = &share([])} = (ASN_COUNTER, 0); @{$snmp_data_n->{$oid.'.1.1.1.13'} = &share([])} = (ASN_COUNTER, 0); @{$snmp_data_n->{$oid.'.1.1.1.14'} = &share([])} = (ASN_COUNTER, 0);
for (1 .. scalar @$clients) {
@{$snmp_data_n->{$oid.'.1.1.1.15.1.1.'.$_} = &share([])} = (ASN_INTEGER, $_); @{$snmp_data_n->{$oid.'.1.1.1.15.1.2.'.$_} = &share([])} = (ASN_IPADDRESS, pack 'C4', split /\./, $clients->[$_-1]->{'FreeRADIUS-Stats-Client-IP-Address'}); @{$snmp_data_n->{$oid.'.1.1.1.15.1.3.'.$_} = &share([])} = (ASN_OCTET_STR, $clients->[$_-1]->{'FreeRADIUS-Stats-Client-Number'}); }
}
sub radius_snmp_stats_init_acct {
my ( $snmp_data_n, $oid, $clients ) = @_;
@{$snmp_data_n->{$oid.'.1.1.1.1'} = &share([])} = (ASN_OCTET_STR, ''); @{$snmp_data_n->{$oid.'.1.1.1.2'} = &share([])} = (ASN_TIMETICKS, 0); @{$snmp_data_n->{$oid.'.1.1.1.3'} = &share([])} = (ASN_TIMETICKS, 0); @{$snmp_data_n->{$oid.'.1.1.1.4'} = &share([])} = (ASN_INTEGER, 0); @{$snmp_data_n->{$oid.'.1.1.1.5'} = &share([])} = (ASN_COUNTER, 0); @{$snmp_data_n->{$oid.'.1.1.1.6'} = &share([])} = (ASN_COUNTER, 0); @{$snmp_data_n->{$oid.'.1.1.1.7'} = &share([])} = (ASN_COUNTER, 0); @{$snmp_data_n->{$oid.'.1.1.1.8'} = &share([])} = (ASN_COUNTER, 0); @{$snmp_data_n->{$oid.'.1.1.1.9'} = &share([])} = (ASN_COUNTER, 0); @{$snmp_data_n->{$oid.'.1.1.1.10'} = &share([])} = (ASN_COUNTER, 0); @{$snmp_data_n->{$oid.'.1.1.1.11'} = &share([])} = (ASN_COUNTER, 0); @{$snmp_data_n->{$oid.'.1.1.1.12'} = &share([])} = (ASN_COUNTER, 0); @{$snmp_data_n->{$oid.'.1.1.1.13'} = &share([])} = (ASN_COUNTER, 0);
for (1 .. scalar @$clients) {
@{$snmp_data_n->{$oid.'.1.1.1.14.1.1.'.$_} = &share([])} = (ASN_INTEGER, $_); @{$snmp_data_n->{$oid.'.1.1.1.14.1.2.'.$_} = &share([])} = (ASN_IPADDRESS, pack 'C4', split /\./, $clients->[$_-1]->{'FreeRADIUS-Stats-Client-IP-Address'}); @{$snmp_data_n->{$oid.'.1.1.1.14.1.3.'.$_} = &share([])} = (ASN_OCTET_STR, $clients->[$_-1]->{'FreeRADIUS-Stats-Client-Number'}); }
}
sub radius_snmp_stats_fill_auth {
my ( $snmp_data_n, $oid, $prefix, $main, $clients ) = @_;
my $time = time;
$snmp_data_n->{$oid.'.1.1.1.1'}->[1] = 'snmp(over)radius';
$snmp_data_n->{$oid.'.1.1.1.2'}->[1] = ($time - $main->{'FreeRADIUS-Stats-Start-Time'})*100;
$snmp_data_n->{$oid.'.1.1.1.3'}->[1] = ($time - $main->{'FreeRADIUS-Stats-HUP-Time'})*100;
$snmp_data_n->{$oid.'.1.1.1.4'}->[1] = 0;
$snmp_data_n->{$oid.'.1.1.1.5'}->[1] += $main->{$prefix.'Access-Requests'};
$snmp_data_n->{$oid.'.1.1.1.6'}->[1] += $main->{$prefix.'Auth-Invalid-Requests'};
$snmp_data_n->{$oid.'.1.1.1.7'}->[1] += $main->{$prefix.'Auth-Duplicate-Requests'};
$snmp_data_n->{$oid.'.1.1.1.8'}->[1] += $main->{$prefix.'Access-Accepts'};
$snmp_data_n->{$oid.'.1.1.1.9'}->[1] += $main->{$prefix.'Access-Rejects'};
$snmp_data_n->{$oid.'.1.1.1.10'}->[1] += $main->{$prefix.'Access-Challenges'};
$snmp_data_n->{$oid.'.1.1.1.11'}->[1] += $main->{$prefix.'Auth-Malformed-Requests'};
$snmp_data_n->{$oid.'.1.1.1.12'}->[1] += 0;
$snmp_data_n->{$oid.'.1.1.1.13'}->[1] += $main->{$prefix.'Auth-Dropped-Requests'};
$snmp_data_n->{$oid.'.1.1.1.14'}->[1] += $main->{$prefix.'Auth-Unknown-Types'};
for (1 .. scalar @$clients) {
}
}
sub radius_snmp_stats_fill_acct {
my ( $snmp_data_n, $oid, $prefix, $main, $clients ) = @_;
my $time = time;
$snmp_data_n->{$oid.'.1.1.1.1'}->[1] = 'snmp(over)radius';
$snmp_data_n->{$oid.'.1.1.1.2'}->[1] = ($time - $main->{'FreeRADIUS-Stats-Start-Time'})*100;
$snmp_data_n->{$oid.'.1.1.1.3'}->[1] = ($time - $main->{'FreeRADIUS-Stats-HUP-Time'})*100;
$snmp_data_n->{$oid.'.1.1.1.4'}->[1] = 0;
$snmp_data_n->{$oid.'.1.1.1.5'}->[1] += $main->{$prefix.'Accounting-Requests'};
$snmp_data_n->{$oid.'.1.1.1.6'}->[1] += $main->{$prefix.'Acct-Invalid-Requests'};
$snmp_data_n->{$oid.'.1.1.1.7'}->[1] += $main->{$prefix.'Acct-Duplicate-Requests'};
$snmp_data_n->{$oid.'.1.1.1.8'}->[1] += $main->{$prefix.'Accounting-Responses'};
$snmp_data_n->{$oid.'.1.1.1.9'}->[1] += $main->{$prefix.'Acct-Malformed-Requests'};
$snmp_data_n->{$oid.'.1.1.1.10'}->[1] += 0;
$snmp_data_n->{$oid.'.1.1.1.11'}->[1] += $main->{$prefix.'Acct-Dropped-Requests'};
$snmp_data_n->{$oid.'.1.1.1.12'}->[1] += 0;
$snmp_data_n->{$oid.'.1.1.1.13'}->[1] += $main->{$prefix.'Acct-Unknown-Types'};
for (1 .. scalar @$clients) {
}
}
sub radius_snmp_stats {
my ( $main, $clients ) = @_;
my %snmp_data_n;
foreach my $oid_s ( keys %{$cfg->{snmp}->{oid_sub}} ) {
my %types = map { $_ => 1 } map { /(?:proxy)?(\w+)/; $1 } @{$cfg->{snmp}->{oid_sub}->{$oid_s}};
WARN 'two conflicting types for oid '.$oid_s if scalar keys %types > 1;
if ( (keys %types)[0] eq 'auth' ) {
radius_snmp_stats_init_auth(\%snmp_data_n, $cfg->{snmp}->{oid_root}.'.'.$oid_s, $clients);
}elsif ( (keys %types)[0] eq 'acct' ) {
radius_snmp_stats_init_acct(\%snmp_data_n, $cfg->{snmp}->{oid_root}.'.'.$oid_s, $clients);
}else {
WARN 'unknown subtree type '.(keys %types)[0];
}
foreach my $type ( @{$cfg->{snmp}->{oid_sub}->{$oid_s}} ) {
if ( $type eq 'auth' ) {
radius_snmp_stats_fill_auth(
\%snmp_data_n, $cfg->{snmp}->{oid_root}.'.'.$oid_s,
'FreeRADIUS-Total-', $main, $clients);
}elsif ( $type eq 'proxyauth' ) {
radius_snmp_stats_fill_auth(
\%snmp_data_n, $cfg->{snmp}->{oid_root}.'.'.$oid_s,
'FreeRADIUS-Total-Proxy-', $main, $clients);
}elsif ( $type eq 'acct' ) {
radius_snmp_stats_fill_acct(
\%snmp_data_n, $cfg->{snmp}->{oid_root}.'.'.$oid_s,
'FreeRADIUS-Total-', $main, $clients);
}elsif ( $type eq 'proxyacct' ) {
radius_snmp_stats_fill_acct(
\%snmp_data_n, $cfg->{snmp}->{oid_root}.'.'.$oid_s,
'FreeRADIUS-Total-Proxy-', $main, $clients);
}else {
WARN 'unknown subtree type '.$type;
}
}
}
lock %snmp_data;
lock @snmp_data_k;
%snmp_data = %snmp_data_n;
@snmp_data_k = map { oid_s($_) } sort { $a <=> $b } map { NetSNMP::OID->new($_) } keys %snmp_data_n;
}
=head1 NAME
freeradius snmp agentx subagent
=head1 VERSION
=head1 SYNOPSIS
make sure snmpd is agentx master (snmpd.conf):
master agentx
run the script (no demonizing support yet):
./freeradius-snmp.pl
then you can walk the tree (default oid):
snmpbulkwalk -On -v2c -cpublic localhost .1.3.6.1.2.1.67
=head1 DESCRIPTION
=head1 DEPENDENCIES
Net-Radius (either 1.56 + net-radius-freeradius-dictionary.diff to use freeradius dictionaries
or vanilla upstream one + dictionary.hacked)
NetSNMP perl modules (available with net-snmp distribution)
Digest::HMAC
Log::Log4perl
=head1 AUTHOR
Stanislaw Sawa <stanislaw.sawa(at)sns.bskyb.com>
=head1 COPYRIGHT
Copyright (C) 2008 Sky Network Services. All Rights Reserved.
This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.