1#! /usr/bin/perl
2#
3# This is an example on a simplistic method to calculate the traffic
4# delta for a given session, based on the RADIUS Accounting
5# packets. The calculation is performed via a database accessed using
6# Net::Radius::Server::DBStore, tied to a persistent backing store.
7#
8# The traffic reported by the Accouting-Request packets is left in two
9# tuples of the RADIUS transaction context, for the enjoyment of later
10# rules that may use this information.
11#
12# Copyright © 2009, Luis E. Muñoz - All Rights Reserved
13#
14# $Id: traffic-delta.pl 111 2009-10-17 23:21:40Z lem $
15
16use strict;
17use warnings;
18
19# We are really using a tied hash for the storage, so you can change
20# these modules to suit your needs.
21
22use MLDBM::Sync;
23use MLDBM qw(DB_File Storable);
24
25use File::Spec::Functions;
26use Net::Radius::Server::Rule;
27use Net::Radius::Server::DBStore;
28use Net::Radius::Server::Base qw/:all/;
29use Net::Radius::Server::Match::Simple;
30
31my @rules         = ();
32my $db_file       = catfile($ENV{HOME}, 'radius-traffic-cache.db');
33my $log_level     = 1;
34
35# Obtain the actual number of octets in and out of a given session,
36# and prepare it for storage in the database.
37
38sub traffic_calc
39{
40    my ($dbstore, $hobj, $r_hash, $r_data, $req, $key) = @_;
41
42    $r_data->{_traffic} = $r_hash->{$key}->{_traffic} || {};
43
44    # Calculate precisely how much traffic have we accounted in this
45    # interface
46    $r_data->{_traffic}->{_in} =
47	(($req->attr('Acct-Input-Gigawords') || 0) * 2 ** 32) +
48	($req->attr('Acct-Input-String') || 0);
49
50    $r_data->{_traffic}->{_out} =
51	(($req->attr('Acct-Output-Gigawords') || 0) * 2 ** 32) +
52	($req->attr('Acct-Output-String') || 0);
53
54    $r_data->{_traffic}->{_type} = ($req->attr('Acct-Status-Type') || '');
55    $r_data->{_traffic}->{_stamp} = time;
56
57    $dbstore->log(4, "Traffic in=" . $r_data->{_traffic}->{_in} .
58		  ", out=" . $r_data->{_traffic}->{_out});
59}
60
61# CAVEAT: This code assumes that Accounting-Request packets will be
62# responded to. Otherwise, you may end up counting the same traffic
63# over and over, until the accounting is acknowledged.
64
65push @rules, Net::Radius::Server::Rule->new
66    ({
67	log_level     => $log_level,
68	# description   => 'Traffic Delta',
69
70	# This match clause looks for a packet that contains
71	# Acct-Session-Id, NAS-IP-Address and basic traffic accounting
72	# data that we can work with
73
74	match_methods => [ Net::Radius::Server::Match::Simple->mk
75			   ({ code => 'Accounting-Request',
76			      attr => [
77				       'NAS-IP-Address'     => qr/./,
78				       'Acct-Session-Id'    => qr/./,
79				       'Acct-Input-String'  => qr/^\d+$/,
80				       'Acct-Output-String' => qr/^\d+$/,
81				       ],
82			      description => 'Acct-Traffic',
83			      log_level   => $log_level }),
84			   ],
85	set_methods   => [
86
87			  # This makes sure that we store the required
88			  # info in our database.
89
90			  Net::Radius::Server::DBStore->mk
91			  ({
92			      log_level        => $log_level,
93			      result           => NRS_SET_CONTINUE,
94			      single           => 1,
95			      frozen           => 0,
96			      description      => 'Traffic-DBStore',
97			      store            => [qw/_traffic/],
98			      pre_store_hook   => \&traffic_calc,
99			      key_attrs        => [
100						   'NAS-IP-Address',
101						   '|',
102						   'Acct-Session-Id',
103						   ],
104				  param        =>
105				  [ 'MLDBM::Sync' => $db_file ],
106			  }),
107			  ],
108    });
109
110return \@rules;
111
112