summaryrefslogtreecommitdiffstats
path: root/scripts/snmp-proxy
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:49:46 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:49:46 +0000
commit50b37d4a27d3295a29afca2286f1a5a086142cec (patch)
tree9212f763934ee090ef72d823f559f52ce387f268 /scripts/snmp-proxy
parentInitial commit. (diff)
downloadfreeradius-50b37d4a27d3295a29afca2286f1a5a086142cec.tar.xz
freeradius-50b37d4a27d3295a29afca2286f1a5a086142cec.zip
Adding upstream version 3.2.1+dfsg.upstream/3.2.1+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--scripts/snmp-proxy/README32
-rw-r--r--scripts/snmp-proxy/dictionary.hacked132
-rw-r--r--scripts/snmp-proxy/freeradius-snmp.pl585
-rw-r--r--scripts/snmp-proxy/net-radius-freeradius-dictionary.diff81
4 files changed, 830 insertions, 0 deletions
diff --git a/scripts/snmp-proxy/README b/scripts/snmp-proxy/README
new file mode 100644
index 0000000..d9741e7
--- /dev/null
+++ b/scripts/snmp-proxy/README
@@ -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
index 0000000..66b2159
--- /dev/null
+++ b/scripts/snmp-proxy/dictionary.hacked
@@ -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
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.
diff --git a/scripts/snmp-proxy/net-radius-freeradius-dictionary.diff b/scripts/snmp-proxy/net-radius-freeradius-dictionary.diff
new file mode 100644
index 0000000..f379cf4
--- /dev/null
+++ b/scripts/snmp-proxy/net-radius-freeradius-dictionary.diff
@@ -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