New SNMP code!
authorAlan T. DeKok <aland@freeradius.org>
Sun, 20 Jul 2008 08:16:21 +0000 (10:16 +0200)
committerAlan T. DeKok <aland@freeradius.org>
Sun, 20 Jul 2008 08:16:21 +0000 (10:16 +0200)
scripts/snmp-proxy/README [new file with mode: 0644]
scripts/snmp-proxy/dictionary.hacked [new file with mode: 0644]
scripts/snmp-proxy/freeradius-snmp.pl [new file with mode: 0644]
scripts/snmp-proxy/net-radius-freeradius-dictionary.diff [new file with mode: 0644]

diff --git a/scripts/snmp-proxy/README b/scripts/snmp-proxy/README
new file mode 100644 (file)
index 0000000..d9741e7
--- /dev/null
@@ -0,0 +1,32 @@
+  The files in this directory replace the old FreeRADIUS SNMP
+implementantion with a new one.
+
+net-radius-freeradius-dictionary.diff
+       Patch to enable the Perl Net::RADIUS module to read the
+       FreeRADIUS dictionary file format.
+
+dictionary.hacked
+       Dictionary used by Perl Net::RADIUS if it is NOT patched.
+       Do NOT use this dictionary with the FreeRADIUS server!
+
+freeradius-snmp.pl
+       Perl module that implements the connection between SNMP
+       and FreeRADIUS.
+
+       See raddb/sites-available/status for information on using
+       Status-Server packets to obtain internal server statistics.
+
+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
+
+This code is ALPHA.  Please test, and return any fixes back to the
+mailing list, or to bugs.freeradius.org.
diff --git a/scripts/snmp-proxy/dictionary.hacked b/scripts/snmp-proxy/dictionary.hacked
new file mode 100644 (file)
index 0000000..66b2159
--- /dev/null
@@ -0,0 +1,132 @@
+#
+#  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
+
diff --git a/scripts/snmp-proxy/freeradius-snmp.pl b/scripts/snmp-proxy/freeradius-snmp.pl
new file mode 100644 (file)
index 0000000..b650151
--- /dev/null
@@ -0,0 +1,464 @@
+#!/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.
diff --git a/scripts/snmp-proxy/net-radius-freeradius-dictionary.diff b/scripts/snmp-proxy/net-radius-freeradius-dictionary.diff
new file mode 100644 (file)
index 0000000..f379cf4
--- /dev/null
@@ -0,0 +1,81 @@
+--- Net-Radius-1.56.orig/Radius/Dictionary.pm  2008-06-20 14:08:57.000000000 +0100
++++ Net-Radius-1.56.1/Radius/Dictionary.pm     2008-06-20 15:54:33.000000000 +0100
+@@ -30,14 +30,23 @@
+ sub readfile {
+   my ($self, $filename) = @_;
++  my $dict;
+-  open DICT, "<$filename";
++  open $dict, "<$filename";
+-  while (defined(my $l = <DICT>)) {
++  my @in_vendor = ();
++
++  while (defined(my $l = <$dict>)) {
+     next if $l =~ /^\#/;
+     next unless my @l = split /\s+/, $l;
+-    if ($l[0] =~ m/^vendor$/i) 
++    if ($l[0] =~ m/^\$include$/i)
++    {
++        my @fn = split /\//, $filename;
++        $fn[$#fn] = $l[1];
++        $self->readfile(join '/', @fn);
++    }
++    elsif ($l[0] =~ m/^vendor$/i) 
+     {
+       if (defined $l[1] and defined $l[2] and $l[2] =~ /^[xo0-9]+$/)
+       {
+@@ -53,8 +62,42 @@
+           warn "Garbled VENDOR line $l\n";
+       }
+     }
++    elsif ($l[0] =~ m/^begin-vendor$/i)
++    {
++    if ( defined $l[1] )
++    {
++        push @in_vendor, $l[1];
++    }
++    else
++    {
++          warn "Garbled BEGIN-VENDOR line $l\n";
++    }
++    }
++    elsif ($l[0] =~ m/^end-vendor$/i)
++    {
++    if ( defined $l[1] )
++    {
++        if ( $in_vendor[$#in_vendor] eq $l[1] ) {
++            pop @in_vendor;
++        }else {
++            warn "mismatched END-VENDOR line $l\n";
++        }
++    }
++    else
++    {
++          warn "Garbled END-VENDOR line $l\n";
++    }
++    }
+     elsif ($l[0] =~ m/^attribute$/i) 
+     {
++    if (@l == 5) {
++        my @tags = grep { not ( m/^encrypt=\d$/ or m/^has_tag$/ ) } split /,/, pop @l;
++        push @l, join ',', @tags if scalar @tags;
++    }
++    if (@l == 4 and scalar @in_vendor) {
++        push @l, $in_vendor[$#in_vendor];
++    }
++
+       if (@l == 4)
+       {
+           $self->{attr}->{$l[1]}  = [@l[2,3]];
+@@ -166,7 +209,7 @@
+       warn "Warning: Weird dictionary line: $l\n";
+     }
+   }
+-  close DICT;
++  close $dict;
+ }
+ # Accessors for standard attributes