From af754e596a8dbb05ed8580c342e7fe02e08b28e0 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 13 Apr 2024 16:11:00 +0200 Subject: Adding upstream version 3.2.3+dfsg. Signed-off-by: Daniel Baumann --- scripts/snmp-proxy/README | 32 ++ scripts/snmp-proxy/dictionary.hacked | 132 +++++ scripts/snmp-proxy/freeradius-snmp.pl | 585 +++++++++++++++++++++ .../net-radius-freeradius-dictionary.diff | 81 +++ 4 files changed, 830 insertions(+) create mode 100644 scripts/snmp-proxy/README create mode 100644 scripts/snmp-proxy/dictionary.hacked create mode 100644 scripts/snmp-proxy/freeradius-snmp.pl create mode 100644 scripts/snmp-proxy/net-radius-freeradius-dictionary.diff (limited to 'scripts/snmp-proxy') 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 + +=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 = )) { ++ 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 -- cgit v1.2.3