--- /dev/null
+#
+# This is a dictionary that should be used by the Perl module Net::RADIUS,
+# if it has NOT been updated to parse the FreeRADIUS format dictionaries.
+#
+# It SHOULD NOT be used in the FreeRADIUS server or client!
+#
+ATTRIBUTE Message-Authenticator 80 octets
+
+
+VENDOR FreeRADIUS 11344
+
+ATTRIBUTE FreeRADIUS-Proxied-To 1 ipaddr
+
+
+#
+# This attribute is really a bitmask.
+#
+ATTRIBUTE FreeRADIUS-Statistics-Type 127 integer FreeRADIUS
+
+VALUE FreeRADIUS-Statistics-Type None 0 FreeRADIUS
+VALUE FreeRADIUS-Statistics-Type Authentication 1
+VALUE FreeRADIUS-Statistics-Type Accounting 2
+VALUE FreeRADIUS-Statistics-Type Proxy-Authentication 4
+VALUE FreeRADIUS-Statistics-Type Proxy-Accounting 8
+VALUE FreeRADIUS-Statistics-Type Internal 0x10
+VALUE FreeRADIUS-Statistics-Type Client 0x20
+VALUE FreeRADIUS-Statistics-Type Server 0x40
+VALUE FreeRADIUS-Statistics-Type Home-Server 0x80
+
+VALUE FreeRADIUS-Statistics-Type Auth-Acct 0x03
+VALUE FreeRADIUS-Statistics-Type Proxy-Auth-Acct 0x0c
+
+VALUE FreeRADIUS-Statistics-Type All 0x1f
+
+#
+# Global authentication statistics for packets received by the server.
+#
+ATTRIBUTE FreeRADIUS-Total-Access-Requests 128 integer FreeRADIUS
+ATTRIBUTE FreeRADIUS-Total-Access-Accepts 129 integer FreeRADIUS
+ATTRIBUTE FreeRADIUS-Total-Access-Rejects 130 integer FreeRADIUS
+ATTRIBUTE FreeRADIUS-Total-Access-Challenges 131 integer FreeRADIUS
+ATTRIBUTE FreeRADIUS-Total-Auth-Responses 132 integer FreeRADIUS
+ATTRIBUTE FreeRADIUS-Total-Auth-Duplicate-Requests 133 integer FreeRADIUS
+ATTRIBUTE FreeRADIUS-Total-Auth-Malformed-Requests 134 integer FreeRADIUS
+ATTRIBUTE FreeRADIUS-Total-Auth-Invalid-Requests 135 integer FreeRADIUS
+ATTRIBUTE FreeRADIUS-Total-Auth-Dropped-Requests 136 integer FreeRADIUS
+ATTRIBUTE FreeRADIUS-Total-Auth-Unknown-Types 137 integer FreeRADIUS
+
+#
+# Global statistics for auth packets sent by the server to all home servers
+#
+ATTRIBUTE FreeRADIUS-Total-Proxy-Access-Requests 138 integer FreeRADIUS
+ATTRIBUTE FreeRADIUS-Total-Proxy-Access-Accepts 139 integer FreeRADIUS
+ATTRIBUTE FreeRADIUS-Total-Proxy-Access-Rejects 140 integer FreeRADIUS
+ATTRIBUTE FreeRADIUS-Total-Proxy-Access-Challenges 141 integer FreeRADIUS
+ATTRIBUTE FreeRADIUS-Total-Proxy-Auth-Responses 142 integer FreeRADIUS
+ATTRIBUTE FreeRADIUS-Total-Proxy-Auth-Duplicate-Requests 143 integer FreeRADIUS
+ATTRIBUTE FreeRADIUS-Total-Proxy-Auth-Malformed-Requests 144 integer FreeRADIUS
+ATTRIBUTE FreeRADIUS-Total-Proxy-Auth-Invalid-Requests 145 integer FreeRADIUS
+ATTRIBUTE FreeRADIUS-Total-Proxy-Auth-Dropped-Requests 146 integer FreeRADIUS
+ATTRIBUTE FreeRADIUS-Total-Proxy-Auth-Unknown-Types 147 integer FreeRADIUS
+
+#
+# Global accounting statistics for packets received by the server.
+#
+ATTRIBUTE FreeRADIUS-Total-Accounting-Requests 148 integer FreeRADIUS
+ATTRIBUTE FreeRADIUS-Total-Accounting-Responses 149 integer FreeRADIUS
+ATTRIBUTE FreeRADIUS-Total-Acct-Duplicate-Requests 150 integer FreeRADIUS
+ATTRIBUTE FreeRADIUS-Total-Acct-Malformed-Requests 151 integer FreeRADIUS
+ATTRIBUTE FreeRADIUS-Total-Acct-Invalid-Requests 152 integer FreeRADIUS
+ATTRIBUTE FreeRADIUS-Total-Acct-Dropped-Requests 153 integer FreeRADIUS
+ATTRIBUTE FreeRADIUS-Total-Acct-Unknown-Types 154 integer FreeRADIUS
+
+#
+# Global statistics for acct packets sent by the server to all home servers
+#
+ATTRIBUTE FreeRADIUS-Total-Proxy-Accounting-Requests 155 integer FreeRADIUS
+ATTRIBUTE FreeRADIUS-Total-Proxy-Accounting-Responses 156 integer FreeRADIUS
+ATTRIBUTE FreeRADIUS-Total-Proxy-Acct-Duplicate-Requests 157 integer FreeRADIUS
+ATTRIBUTE FreeRADIUS-Total-Proxy-Acct-Malformed-Requests 158 integer FreeRADIUS
+ATTRIBUTE FreeRADIUS-Total-Proxy-Acct-Invalid-Requests 159 integer FreeRADIUS
+ATTRIBUTE FreeRADIUS-Total-Proxy-Acct-Dropped-Requests 160 integer FreeRADIUS
+ATTRIBUTE FreeRADIUS-Total-Proxy-Acct-Unknown-Types 161 integer FreeRADIUS
+
+#
+# Internal queues. Different packet types are put into different queues.
+#
+ATTRIBUTE FreeRADIUS-Queue-Len-Internal 162 integer FreeRADIUS
+ATTRIBUTE FreeRADIUS-Queue-Len-Proxy 163 integer FreeRADIUS
+ATTRIBUTE FreeRADIUS-Queue-Len-Auth 164 integer FreeRADIUS
+ATTRIBUTE FreeRADIUS-Queue-Len-Acct 165 integer FreeRADIUS
+ATTRIBUTE FreeRADIUS-Queue-Len-Detail 166 integer FreeRADIUS
+
+ATTRIBUTE FreeRADIUS-Stats-Client-IP-Address 167 ipaddr FreeRADIUS
+ATTRIBUTE FreeRADIUS-Stats-Client-Number 168 integer FreeRADIUS
+ATTRIBUTE FreeRADIUS-Stats-Client-Netmask 169 integer FreeRADIUS
+
+ATTRIBUTE FreeRADIUS-Stats-Server-IP-Address 170 ipaddr FreeRADIUS
+ATTRIBUTE FreeRADIUS-Stats-Server-Port 171 integer FreeRADIUS
+
+ATTRIBUTE FreeRADIUS-Stats-Server-Outstanding-Requests 172 integer FreeRADIUS
+ATTRIBUTE FreeRADIUS-Stats-Server-State 173 integer FreeRADIUS
+
+VALUE FreeRADIUS-Stats-Server-State Alive 0
+VALUE FreeRADIUS-Stats-Server-State Zombie 1
+VALUE FreeRADIUS-Stats-Server-State Dead 2
+
+#
+# When a home server is marked "dead" or "alive"
+#
+ATTRIBUTE FreeRADIUS-Stats-Server-Time-Of-Death 174 date FreeRADIUS
+ATTRIBUTE FreeRADIUS-Stats-Server-Time-Of-Life 175 date FreeRADIUS
+
+#
+# When this server was started. If start == hup, it hasn't been
+# hup'd yet. This is friendlier than having hup == 0 on start.
+#
+ATTRIBUTE FreeRADIUS-Stats-Start-Time 176 date FreeRADIUS
+ATTRIBUTE FreeRADIUS-Stats-HUP-Time 177 date FreeRADIUS
+
+#
+# Exponential moving average of home server response time
+# Window-1 is the average is calculated over "window" packets.
+# Window-10 is the average is calculated over "10 * window" packets.
+#
+# Both Window-1 and Window-10 are times in microseconds
+# (1/1000000 of a second).
+#
+ATTRIBUTE FreeRADIUS-Server-EMA-Window 178 integer FreeRADIUS
+ATTRIBUTE FreeRADIUS-Server-EMA-USEC-Window-1 179 integer FreeRADIUS
+ATTRIBUTE FreeRADIUS-Server-EMA-USEC-Window-10 180 integer FreeRADIUS
+
--- /dev/null
+#!/usr/bin/perl
+#
+# 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.
+#
+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/;
+#use Data::Dumper;
+#$Data::Dumper::Indent = 1;
+#$Data::Dumper::Sortkeys = 1;
+#$| = 1;
+
+# config. should be really loaded from some file
+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 => $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}});
+
+#lets create the thread as early as possible (but it has to be AFTER initializing snmp)
+INFO 'launching radius client thread';
+threads->create(\&radius_updater);
+
+#we export only subtrees, not the whole tree
+$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;
+
+
+# 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 {
+ 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;
+ }
+ }
+
+ #todo ng: update on demand, and update only parts of snmp visible stats
+
+ 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};
+ }
+
+}
+
+#helper to get string from NetSNMP::OID
+sub oid_s { return join '.', $_[0]->to_array; }
+
+#handler for snmp requests from master agent(clients)
+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 ) {
+ #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) ) {
+ $request->setValue($snmp_data{$oid}->[0], ''.$snmp_data{$oid}->[1]);
+ $request->setOID($oid);
+ last;
+ }
+ }
+
+ }else {
+ #no write support
+ $request->setError($request_info, SNMP_ERR_READONLY);
+
+ }
+ }
+ 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
+
+ #radiusAuthClientTable
+ for (1 .. scalar @$clients) {
+ @{$snmp_data_n->{$oid.'.1.1.1.15.1.1.'.$_} = &share([])} = (ASN_INTEGER, $_); #radiusAuthClientIndex
+ @{$snmp_data_n->{$oid.'.1.1.1.15.1.2.'.$_} = &share([])} = (ASN_IPADDRESS, pack 'C4', split /\./, $clients->[$_-1]->{'FreeRADIUS-Stats-Client-IP-Address'}); #radiusAuthClientAddress
+ @{$snmp_data_n->{$oid.'.1.1.1.15.1.3.'.$_} = &share([])} = (ASN_OCTET_STR, $clients->[$_-1]->{'FreeRADIUS-Stats-Client-Number'}); #radiusAuthClientID
+# @{$snmp_data_n->{$oid.'.1.1.1.15.1.4.'.$_} = &share([])} = (ASN_COUNTER, 0); #radiusAuthServAccessRequests
+# @{$snmp_data_n->{$oid.'.1.1.1.15.1.5.'.$_} = &share([])} = (ASN_COUNTER, 0); #radiusAuthServDupAccessRequests
+# @{$snmp_data_n->{$oid.'.1.1.1.15.1.6.'.$_} = &share([])} = (ASN_COUNTER, 0); #radiusAuthServAccessAccepts
+# @{$snmp_data_n->{$oid.'.1.1.1.15.1.7.'.$_} = &share([])} = (ASN_COUNTER, 0); #radiusAuthServAccessRejects
+# @{$snmp_data_n->{$oid.'.1.1.1.15.1.8.'.$_} = &share([])} = (ASN_COUNTER, 0); #radiusAuthServAccessChallenges
+# @{$snmp_data_n->{$oid.'.1.1.1.15.1.9.'.$_} = &share([])} = (ASN_COUNTER, 0); #radiusAuthServMalformedAccessRequests
+# @{$snmp_data_n->{$oid.'.1.1.1.15.1.10.'.$_} = &share([])} = (ASN_COUNTER, 0); #radiusAuthServBadAuthenticators
+# @{$snmp_data_n->{$oid.'.1.1.1.15.1.11.'.$_} = &share([])} = (ASN_COUNTER, 0); #radiusAuthServPacketsDropped
+# @{$snmp_data_n->{$oid.'.1.1.1.15.1.12.'.$_} = &share([])} = (ASN_COUNTER, 0); #radiusAuthServUnknownTypes
+ }
+}
+
+#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
+
+ #radiusAccClientTable
+ for (1 .. scalar @$clients) {
+ @{$snmp_data_n->{$oid.'.1.1.1.14.1.1.'.$_} = &share([])} = (ASN_INTEGER, $_); #radiusAccClientIndex
+ @{$snmp_data_n->{$oid.'.1.1.1.14.1.2.'.$_} = &share([])} = (ASN_IPADDRESS, pack 'C4', split /\./, $clients->[$_-1]->{'FreeRADIUS-Stats-Client-IP-Address'}); #radiusAccClientAddress
+ @{$snmp_data_n->{$oid.'.1.1.1.14.1.3.'.$_} = &share([])} = (ASN_OCTET_STR, $clients->[$_-1]->{'FreeRADIUS-Stats-Client-Number'}); #radiusAccClientID
+# @{$snmp_data_n->{$oid.'.1.1.1.14.1.4.'.$_} = &share([])} = (ASN_COUNTER, 0); #radiusAccServPacketsDropped
+# @{$snmp_data_n->{$oid.'.1.1.1.14.1.5.'.$_} = &share([])} = (ASN_COUNTER, 0); #radiusAccServRequests
+# @{$snmp_data_n->{$oid.'.1.1.1.14.1.6.'.$_} = &share([])} = (ASN_COUNTER, 0); #radiusAccServDupRequests
+# @{$snmp_data_n->{$oid.'.1.1.1.14.1.7.'.$_} = &share([])} = (ASN_COUNTER, 0); #radiusAccServResponses
+# @{$snmp_data_n->{$oid.'.1.1.1.14.1.8.'.$_} = &share([])} = (ASN_COUNTER, 0); #radiusAccServBadAuthenticators
+# @{$snmp_data_n->{$oid.'.1.1.1.14.1.9.'.$_} = &share([])} = (ASN_COUNTER, 0); #radiusAccServMalformedRequests
+# @{$snmp_data_n->{$oid.'.1.1.1.14.1.10.'.$_} = &share([])} = (ASN_COUNTER, 0); #radiusAccServNoRecords
+# @{$snmp_data_n->{$oid.'.1.1.1.14.1.11.'.$_} = &share([])} = (ASN_COUNTER, 0); #radiusAccServUnknownTypes
+ }
+}
+
+#fill part of subtree with data from radius auth statistics
+sub radius_snmp_stats_fill_auth {
+ my ( $snmp_data_n, $oid, $prefix, $main, $clients ) = @_;
+ #hmm .. proxy?
+
+ 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) {
+# $snmp_data_n->{$oid.'.1.1.1.15.1.4.'.$_}->[1] += $clients->[$_-1]->{$prefix.'Access-Requests'};
+# $snmp_data_n->{$oid.'.1.1.1.15.1.5.'.$_}->[1] += $clients->[$_-1]->{$prefix.'Auth-Duplicate-Requests'};
+# $snmp_data_n->{$oid.'.1.1.1.15.1.6.'.$_}->[1] += $clients->[$_-1]->{$prefix.'Access-Accepts'};
+# $snmp_data_n->{$oid.'.1.1.1.15.1.7.'.$_}->[1] += $clients->[$_-1]->{$prefix.'Access-Rejects'};
+# $snmp_data_n->{$oid.'.1.1.1.15.1.8.'.$_}->[1] += $clients->[$_-1]->{$prefix.'Access-Challenges'};
+# $snmp_data_n->{$oid.'.1.1.1.15.1.9.'.$_}->[1] += $clients->[$_-1]->{$prefix.'Auth-Malformed-Requests'};
+# $snmp_data_n->{$oid.'.1.1.1.15.1.10.'.$_}->[1] += 0;
+# $snmp_data_n->{$oid.'.1.1.1.15.1.11.'.$_}->[1] += $clients->[$_-1]->{$prefix.'Auth-Dropped-Requests'};
+# $snmp_data_n->{$oid.'.1.1.1.15.1.12.'.$_}->[1] += $clients->[$_-1]->{$prefix.'Auth-Unknown-Types'};
+ }
+}
+
+#fill part of subtree with data from radius acct statistics
+sub radius_snmp_stats_fill_acct {
+ my ( $snmp_data_n, $oid, $prefix, $main, $clients ) = @_;
+ #hmm .. proxy?
+
+ 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) {
+# $snmp_data_n->{$oid.'.1.1.1.14.1.4.'.$_}->[1] += $clients->[$_-1]->{$prefix.''};# 'radiusAccServPacketsDropped';
+# $snmp_data_n->{$oid.'.1.1.1.14.1.5.'.$_}->[1] += $clients->[$_-1]->{$prefix.''};# 'radiusAccServRequests';
+# $snmp_data_n->{$oid.'.1.1.1.14.1.6.'.$_}->[1] += $clients->[$_-1]->{$prefix.''};# 'radiusAccServDupRequests';
+# $snmp_data_n->{$oid.'.1.1.1.14.1.7.'.$_}->[1] += $clients->[$_-1]->{$prefix.''};# 'radiusAccServResponses';
+# $snmp_data_n->{$oid.'.1.1.1.14.1.8.'.$_}->[1] += $clients->[$_-1]->{$prefix.''};# 'radiusAccServBadAuthenticators';
+# $snmp_data_n->{$oid.'.1.1.1.14.1.9.'.$_}->[1] += $clients->[$_-1]->{$prefix.''};# 'radiusAccServMalformedRequests';
+# $snmp_data_n->{$oid.'.1.1.1.14.1.10.'.$_}->[1] += $clients->[$_-1]->{$prefix.''};# 'radiusAccServNoRecords';
+# $snmp_data_n->{$oid.'.1.1.1.14.1.11.'.$_}->[1] += $clients->[$_-1]->{$prefix.''};# 'radiusAccServUnknownTypes';
+ }
+}
+
+#update statistics
+sub radius_snmp_stats {
+ my ( $main, $clients ) = @_;
+
+#print Dumper($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];
+
+ }
+
+ #now lets 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;
+ }
+
+ }
+ }
+
+ #we rebuild the tree, so lets now lock the shared variables and push new data there
+ 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.