summaryrefslogtreecommitdiffstats
path: root/scripts/snmp-proxy/freeradius-snmp.pl
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/snmp-proxy/freeradius-snmp.pl')
-rw-r--r--scripts/snmp-proxy/freeradius-snmp.pl585
1 files changed, 585 insertions, 0 deletions
diff --git a/scripts/snmp-proxy/freeradius-snmp.pl b/scripts/snmp-proxy/freeradius-snmp.pl
new file mode 100644
index 0000000..f30fc7d
--- /dev/null
+++ b/scripts/snmp-proxy/freeradius-snmp.pl
@@ -0,0 +1,585 @@
+#!/usr/bin/perl
+#
+# Copyright (C) 2008 Sky Network Services.
+# Copyright (C) 2022 Network RADIUS.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the same terms as Perl itself.
+#
+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 Socket qw(inet_ntop);
+use IO::Socket::INET;
+use Digest::HMAC_MD5;
+use Log::Log4perl qw/:easy/;
+#use Data::Dumper;
+#$Data::Dumper::Indent = 1;
+#$Data::Dumper::Sortkeys = 1;
+#$| = 1;
+
+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 => '../radiusd/share/dictionary',
+ dictionary => 'dictionary.hacked',
+ refresh_rate => 20,
+ },
+
+ log => {
+ level => $WARN, # $DEBUG, $ERROR, etc.
+ layout => '%d{ISO8601} <%p> (%L) %m%n',
+ file => 'STDERR'
+ },
+
+ clients => 1, # Or 0 to disable
+};
+
+Log::Log4perl->easy_init($cfg->{log});
+
+INFO 'starting';
+my $running :shared;
+my %snmp_data :shared;
+my @snmp_data_k :shared;
+my %snmp_next :shared;
+
+INFO 'initializing snmp';
+my $agent = new NetSNMP::agent(%{$cfg->{snmp}->{agent}});
+
+radius_stats_init();
+$running = 1;
+
+$SIG{INT} = sub {
+ INFO 'stopping';
+ $running = 0;
+};
+
+#
+# Background updater thread
+#
+INFO 'launching radius client thread';
+threads->create(\&radius_updater);
+
+#
+# We export only the radiusAuthServ and radiusAccServ subtree
+#
+$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->agent_check_and_process(1) while $running;
+
+$agent->shutdown();
+
+$_->join() for threads->list();
+
+
+#
+# Initialize common radius client stuff
+#
+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;
+
+}
+
+#
+# Build server status packet, send it, fetch and parse the result
+#
+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;
+
+ #
+ # Update id
+ #
+ $p_req->set_identifier($rid++);
+ $p_req->set_authenticator(pack 'C*', map { int rand 255 } 0..15);
+
+ #
+ # Recalc authenticator
+ #
+ $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);
+
+ #
+ # Send brand new and shiny request
+ #
+ $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;
+ }
+
+}
+
+#
+# Wrappers for specific types of stats
+#
+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]); }
+
+#
+# Main loop of thread fetching status from freeradius server
+#
+sub radius_updater {
+
+ while ($running) {
+ 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'} || exists $client_stat->{'FreeRADIUS-Stats-Client-IPv6-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';
+ my $now = time;
+ my $next_stats_time = $now + $cfg->{radius}->{refresh_rate};
+ do {
+ sleep 1;
+ $now = time;
+ } while ($now < $next_stats_time && $running);
+
+ }
+
+}
+
+#
+# Helper to get a dotted string from NetSNMP::OID
+#
+sub oid_s { return join '.', $_[0]->to_array; }
+
+#
+# Handler for snmp requests from master agent
+#
+sub snmp_handler {
+ DEBUG 'got new request';
+ my ($handler, $registration_info, $request_info, $requests) = @_;
+
+ lock %snmp_data;
+ lock @snmp_data_k;
+ lock %snmp_next;
+
+ 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) {
+
+ #
+ # Do a fast lookup if we can...
+ #
+ my $oid = $snmp_next{oid_s($request->getOID)};
+
+ #
+ # ... otherwise take the slow route
+ #
+ unless (defined $oid) {
+ foreach ( @snmp_data_k ) {
+ #the keys is sorted in ascending order, so we are looking for
+ #first value bigger than one in request
+ if ($request->getOID < NetSNMP::OID->new($_)) {
+ $oid = $_;
+ last;
+ }
+ }
+ }
+
+ next unless $oid;
+
+ $request->setValue($snmp_data{$oid}->[0], ''.$snmp_data{$oid}->[1]);
+ $request->setOID($oid);
+
+ }
+ else {
+ $request->setError($request_info, SNMP_ERR_READONLY); # No write support
+ }
+ }
+ DEBUG 'finished processing the request';
+}
+
+#
+# Init part of subtree for handling radius AUTH statistics
+#
+sub radius_snmp_stats_init_auth {
+ my ($snmp_data_n, $oid, $clients) = @_;
+
+ @{$snmp_data_n->{$oid.'.1.1.1.1'} = &share([])} = (ASN_OCTET_STR, ''); # radiusAuthServIdent
+ @{$snmp_data_n->{$oid.'.1.1.1.2'} = &share([])} = (ASN_TIMETICKS, 0); # radiusAuthServUpTime
+ @{$snmp_data_n->{$oid.'.1.1.1.3'} = &share([])} = (ASN_TIMETICKS, 0); # radiusAuthServResetTime
+ @{$snmp_data_n->{$oid.'.1.1.1.4'} = &share([])} = (ASN_INTEGER, 0); # radiusAuthServConfigReset
+ @{$snmp_data_n->{$oid.'.1.1.1.5'} = &share([])} = (ASN_COUNTER, 0); # radiusAuthServTotalAccessRequests
+ @{$snmp_data_n->{$oid.'.1.1.1.6'} = &share([])} = (ASN_COUNTER, 0); # radiusAuthServTotalInvalidRequests
+ @{$snmp_data_n->{$oid.'.1.1.1.7'} = &share([])} = (ASN_COUNTER, 0); # radiusAuthServTotalDupAccessRequests
+ @{$snmp_data_n->{$oid.'.1.1.1.8'} = &share([])} = (ASN_COUNTER, 0); # radiusAuthServTotalAccessAccepts
+ @{$snmp_data_n->{$oid.'.1.1.1.9'} = &share([])} = (ASN_COUNTER, 0); # radiusAuthServTotalAccessRejects
+ @{$snmp_data_n->{$oid.'.1.1.1.10'} = &share([])} = (ASN_COUNTER, 0); # radiusAuthServTotalAccessChallenges
+ @{$snmp_data_n->{$oid.'.1.1.1.11'} = &share([])} = (ASN_COUNTER, 0); # radiusAuthServTotalMalformedAccessRequests
+ @{$snmp_data_n->{$oid.'.1.1.1.12'} = &share([])} = (ASN_COUNTER, 0); # radiusAuthServTotalBadAuthenticators
+ @{$snmp_data_n->{$oid.'.1.1.1.13'} = &share([])} = (ASN_COUNTER, 0); # radiusAuthServTotalPacketsDropped
+ @{$snmp_data_n->{$oid.'.1.1.1.14'} = &share([])} = (ASN_COUNTER, 0); # radiusAuthServTotalUnknownTypes
+
+ #
+ # radiusAuthClientExtTable
+ #
+ for (1 .. scalar @$clients) {
+
+ my $addrtype;
+ my $addr;
+ if (exists $clients->[$_-1]->{'FreeRADIUS-Stats-Client-IP-Address'}) {
+ $addrtype = 1;
+ $addr = $clients->[$_-1]->{'FreeRADIUS-Stats-Client-IP-Address'};
+ }
+ elsif (exists $clients->[$_-1]->{'FreeRADIUS-Stats-Client-IPv6-Address'}) {
+ $addrtype = 2;
+ $addr = inet_ntop(AF_INET6, $clients->[$_-1]->{'FreeRADIUS-Stats-Client-IPv6-Address'});
+ }
+ else {
+ next;
+ }
+
+ @{$snmp_data_n->{$oid.'.1.1.1.16.1.1.'.$_} = &share([])} = (ASN_INTEGER, $_); # radiusAuthClientExtIndex
+ @{$snmp_data_n->{$oid.'.1.1.1.16.1.2.'.$_} = &share([])} = (ASN_INTEGER, $addrtype); # radiusAuthClientInetAddressType
+ @{$snmp_data_n->{$oid.'.1.1.1.16.1.3.'.$_} = &share([])} = (ASN_OCTET_STR, $addr); # radiusAuthClientInetAddress
+ @{$snmp_data_n->{$oid.'.1.1.1.16.1.4.'.$_} = &share([])} = (ASN_OCTET_STR, $clients->[$_-1]->{'FreeRADIUS-Stats-Client-Number'}); # radiusAuthClientExtID
+ @{$snmp_data_n->{$oid.'.1.1.1.16.1.5.'.$_} = &share([])} = (ASN_COUNTER, 0); # radiusAuthServExtAccessRequests
+ @{$snmp_data_n->{$oid.'.1.1.1.16.1.6.'.$_} = &share([])} = (ASN_COUNTER, 0); # radiusAuthServExtDupAccessRequests
+ @{$snmp_data_n->{$oid.'.1.1.1.16.1.7.'.$_} = &share([])} = (ASN_COUNTER, 0); # radiusAuthServExtAccessAccepts
+ @{$snmp_data_n->{$oid.'.1.1.1.16.1.8.'.$_} = &share([])} = (ASN_COUNTER, 0); # radiusAuthServExtAccessRejects
+ @{$snmp_data_n->{$oid.'.1.1.1.16.1.9.'.$_} = &share([])} = (ASN_COUNTER, 0); # radiusAuthServExtAccessChallenges
+ @{$snmp_data_n->{$oid.'.1.1.1.16.1.10.'.$_} = &share([])} = (ASN_COUNTER, 0); # radiusAuthServExtMalformedAccessRequests
+ @{$snmp_data_n->{$oid.'.1.1.1.16.1.11.'.$_} = &share([])} = (ASN_COUNTER, 0); # radiusAuthServExtBadAuthenticators
+ @{$snmp_data_n->{$oid.'.1.1.1.16.1.12.'.$_} = &share([])} = (ASN_COUNTER, 0); # radiusAuthServExtPacketsDropped
+ @{$snmp_data_n->{$oid.'.1.1.1.16.1.13.'.$_} = &share([])} = (ASN_COUNTER, 0); # radiusAuthServExtUnknownTypes
+ @{$snmp_data_n->{$oid.'.1.1.1.16.1.14.'.$_} = &share([])} = (ASN_TIMETICKS, 0); # radiusAuthServerCounterDiscontinuity
+
+ }
+}
+
+#
+# Init part of subtree for handling radius ACCT statistics
+#
+sub radius_snmp_stats_init_acct {
+ my ( $snmp_data_n, $oid, $clients ) = @_;
+
+ @{$snmp_data_n->{$oid.'.1.1.1.1'} = &share([])} = (ASN_OCTET_STR, ''); # radiusAccServIdent
+ @{$snmp_data_n->{$oid.'.1.1.1.2'} = &share([])} = (ASN_TIMETICKS, 0); # radiusAccServUpTime
+ @{$snmp_data_n->{$oid.'.1.1.1.3'} = &share([])} = (ASN_TIMETICKS, 0); # radiusAccServResetTime
+ @{$snmp_data_n->{$oid.'.1.1.1.4'} = &share([])} = (ASN_INTEGER, 0); # radiusAccServConfigReset
+ @{$snmp_data_n->{$oid.'.1.1.1.5'} = &share([])} = (ASN_COUNTER, 0); # radiusAccServTotalRequests
+ @{$snmp_data_n->{$oid.'.1.1.1.6'} = &share([])} = (ASN_COUNTER, 0); # radiusAccServTotalInvalidRequests
+ @{$snmp_data_n->{$oid.'.1.1.1.7'} = &share([])} = (ASN_COUNTER, 0); # radiusAccServTotalDupRequests
+ @{$snmp_data_n->{$oid.'.1.1.1.8'} = &share([])} = (ASN_COUNTER, 0); # radiusAccServTotalResponses
+ @{$snmp_data_n->{$oid.'.1.1.1.9'} = &share([])} = (ASN_COUNTER, 0); # radiusAccServTotalMalformedRequests
+ @{$snmp_data_n->{$oid.'.1.1.1.10'} = &share([])} = (ASN_COUNTER, 0); # radiusAccServTotalBadAuthenticators
+ @{$snmp_data_n->{$oid.'.1.1.1.11'} = &share([])} = (ASN_COUNTER, 0); # radiusAccServTotalPacketsDropped
+ @{$snmp_data_n->{$oid.'.1.1.1.12'} = &share([])} = (ASN_COUNTER, 0); # radiusAccServTotalNoRecords
+ @{$snmp_data_n->{$oid.'.1.1.1.13'} = &share([])} = (ASN_COUNTER, 0); # radiusAccServTotalUnknownTypes
+
+ #
+ # radiusAccClientExtTable
+ #
+ for (1 .. scalar @$clients) {
+
+ my $addrtype;
+ my $addr;
+ if (exists $clients->[$_-1]->{'FreeRADIUS-Stats-Client-IP-Address'}) {
+ $addrtype = 1;
+ $addr = $clients->[$_-1]->{'FreeRADIUS-Stats-Client-IP-Address'};
+ }
+ elsif (exists $clients->[$_-1]->{'FreeRADIUS-Stats-Client-IPv6-Address'}) {
+ $addrtype = 2;
+ $addr = inet_ntop(AF_INET6, $clients->[$_-1]->{'FreeRADIUS-Stats-Client-IPv6-Address'});
+ }
+ else {
+ next;
+ }
+
+ @{$snmp_data_n->{$oid.'.1.1.1.15.1.1.'.$_} = &share([])} = (ASN_INTEGER, $_); # radiusAccClientExtIndex
+ @{$snmp_data_n->{$oid.'.1.1.1.15.1.2.'.$_} = &share([])} = (ASN_INTEGER, $addrtype); # radiusAccClientInetAddressType
+ @{$snmp_data_n->{$oid.'.1.1.1.15.1.3.'.$_} = &share([])} = (ASN_OCTET_STR, $addr); # radiusAccClientInetAddress
+ @{$snmp_data_n->{$oid.'.1.1.1.15.1.4.'.$_} = &share([])} = (ASN_OCTET_STR, $clients->[$_-1]->{'FreeRADIUS-Stats-Client-Number'}); # radiusAccClientExtID
+ @{$snmp_data_n->{$oid.'.1.1.1.15.1.5.'.$_} = &share([])} = (ASN_COUNTER, 0); # radiusAccServExtPacketsDropped
+ @{$snmp_data_n->{$oid.'.1.1.1.15.1.6.'.$_} = &share([])} = (ASN_COUNTER, 0); # radiusAccServExtRequests
+ @{$snmp_data_n->{$oid.'.1.1.1.15.1.7.'.$_} = &share([])} = (ASN_COUNTER, 0); # radiusAccServExtDupRequests
+ @{$snmp_data_n->{$oid.'.1.1.1.15.1.8.'.$_} = &share([])} = (ASN_COUNTER, 0); # radiusAccServResponses
+ @{$snmp_data_n->{$oid.'.1.1.1.15.1.9.'.$_} = &share([])} = (ASN_COUNTER, 0); # radiusAccServExtBadAuthenticators
+ @{$snmp_data_n->{$oid.'.1.1.1.15.1.10.'.$_} = &share([])} = (ASN_COUNTER, 0); # radiusAccServExtMalformedRequests
+ @{$snmp_data_n->{$oid.'.1.1.1.15.1.11.'.$_} = &share([])} = (ASN_COUNTER, 0); # radiusAccServExtNoRecords
+ @{$snmp_data_n->{$oid.'.1.1.1.15.1.12.'.$_} = &share([])} = (ASN_COUNTER, 0); # radiusAccServExtUnknownTypes
+ @{$snmp_data_n->{$oid.'.1.1.1.15.1.13.'.$_} = &share([])} = (ASN_TIMETICKS, 0); # radiusAccServerCounterDiscontinuity
+
+ }
+}
+
+#
+# Fill part of subtree with data from radius AUTH statistics
+#
+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) {
+ next unless exists $clients->[$_-1]->{'FreeRADIUS-Stats-Client-IP-Address'} || exists $clients->[$_-1]->{'FreeRADIUS-Stats-Client-IPv6-Address'};
+ $snmp_data_n->{$oid.'.1.1.1.16.1.5.'.$_}->[1] += $clients->[$_-1]->{$prefix.'Access-Requests'} || 0;
+ $snmp_data_n->{$oid.'.1.1.1.16.1.6.'.$_}->[1] += $clients->[$_-1]->{$prefix.'Auth-Duplicate-Requests'} || 0;
+ $snmp_data_n->{$oid.'.1.1.1.16.1.7.'.$_}->[1] += $clients->[$_-1]->{$prefix.'Access-Accepts'} || 0;
+ $snmp_data_n->{$oid.'.1.1.1.16.1.8.'.$_}->[1] += $clients->[$_-1]->{$prefix.'Access-Rejects'} || 0;
+ $snmp_data_n->{$oid.'.1.1.1.16.1.9.'.$_}->[1] += $clients->[$_-1]->{$prefix.'Access-Challenges'} || 0;
+ $snmp_data_n->{$oid.'.1.1.1.16.1.10.'.$_}->[1] += $clients->[$_-1]->{$prefix.'Auth-Malformed-Requests'} || 0;
+ $snmp_data_n->{$oid.'.1.1.1.16.1.11.'.$_}->[1] += 0;
+ $snmp_data_n->{$oid.'.1.1.1.16.1.12.'.$_}->[1] += $clients->[$_-1]->{$prefix.'Auth-Dropped-Requests'} || 0;
+ $snmp_data_n->{$oid.'.1.1.1.16.1.13.'.$_}->[1] += $clients->[$_-1]->{$prefix.'Auth-Unknown-Types'} || 0;
+ $snmp_data_n->{$oid.'.1.1.1.16.1.14.'.$_}->[1] += 0;
+ }
+}
+
+#
+# Fill part of subtree with data from radius ACCT statistics
+#
+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) {
+ next unless exists $clients->[$_-1]->{'FreeRADIUS-Stats-Client-IP-Address'} || exists $clients->[$_-1]->{'FreeRADIUS-Stats-Client-IPv6-Address'};
+ $snmp_data_n->{$oid.'.1.1.1.15.1.5.'.$_}->[1] += $clients->[$_-1]->{$prefix.'Acct-Dropped-Requests'} || 0;
+ $snmp_data_n->{$oid.'.1.1.1.15.1.6.'.$_}->[1] += $clients->[$_-1]->{$prefix.'Accounting-Requests'} || 0;
+ $snmp_data_n->{$oid.'.1.1.1.15.1.7.'.$_}->[1] += $clients->[$_-1]->{$prefix.'Acct-Duplicate-Requests'} || 0;
+ $snmp_data_n->{$oid.'.1.1.1.15.1.8.'.$_}->[1] += $clients->[$_-1]->{$prefix.'Accounting-Responses'} || 0;
+ $snmp_data_n->{$oid.'.1.1.1.15.1.9.'.$_}->[1] += 0;
+ $snmp_data_n->{$oid.'.1.1.1.15.1.10.'.$_}->[1] += $clients->[$_-1]->{$prefix.'Acct-Malformed-Requests'} || 0;
+ $snmp_data_n->{$oid.'.1.1.1.15.1.11.'.$_}->[1] += 0;
+ $snmp_data_n->{$oid.'.1.1.1.15.1.12.'.$_}->[1] += $clients->[$_-1]->{$prefix.'Acct-Unknown-Types'} || 0;
+ $snmp_data_n->{$oid.'.1.1.1.15.1.13.'.$_}->[1] += 0;
+ }
+}
+
+#
+# Update statistics
+#
+sub radius_snmp_stats {
+ my ($main, $clients) = @_;
+
+ my %snmp_data_n;
+
+ #
+ # We have to go through all oid's
+ #
+ foreach my $oid_s ( keys %{$cfg->{snmp}->{oid_sub}} ) {
+
+ #
+ # We're rebuilding the tree for data. We could do it only once, but it
+ # will change when we will start handling more dynamic tree (clients)
+ #
+ 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];
+ }
+
+ #
+ # Refill the statistics
+ #
+ 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;
+ }
+
+ }
+ }
+
+ #
+ # Copy the rebuilt tree to the shared variables
+ #
+ my @k = map { oid_s($_) } sort { $a <=> $b } map { NetSNMP::OID->new($_) } keys %snmp_data_n;
+ my %snmp_next_n = ();
+ $snmp_next_n{$k[$_]} = $k[$_+1] for (0 .. $#k-1);
+
+ lock %snmp_data;
+ lock @snmp_data_k;
+ lock %snmp_next;
+
+ %snmp_data = %snmp_data_n;
+ @snmp_data_k = @k;
+ %snmp_next = %snmp_next_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
+
+if per-client stats collection is enabled then you can do the following:
+
+snmptable -v2c -cpublic localhost .1.3.6.1.2.1.67.1.1.1.1.16
+snmptable -v2c -cpublic localhost .1.3.6.1.2.1.67.2.1.1.1.15
+
+=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.
+Copyright (C) 2022 Network RADIUS.
+
+This program is free software; you can redistribute it and/or modify it
+under the same terms as Perl itself.