File Coverage

blib/lib/Cisco/Accounting.pm
Criterion Covered Total %
statement 18 226 7.9
branch 0 98 0.0
condition 0 34 0.0
subroutine 6 24 25.0
pod 12 12 100.0
total 36 394 9.1


line stmt bran cond sub pod time code
1             package Cisco::Accounting;
2              
3             ## ----------------------------------------------------------------------------------------------
4             ## Cisco::Accounting
5             ##
6             ## Cisco and IPCAD ip accounting parser and aggregator
7             ##
8             ## $Id: Accounting.pm 125 2007-08-18 22:10:25Z mwallraf $
9             ## $Author: mwallraf $
10             ## $Date: 2007-08-19 00:10:25 +0200 (Sun, 19 Aug 2007) $
11             ##
12             ## This program is free software; you can redistribute it and/or
13             ## modify it under the same terms as Perl itself.
14             ## ----------------------------------------------------------------------------------------------
15              
16              
17             our $VERSION = '1.01';
18              
19 1     1   22576 use warnings;
  1         2  
  1         28  
20 1     1   4 use strict;
  1         1  
  1         29  
21 1     1   5 use Carp;
  1         13  
  1         69  
22             #use Data::Dumper; ## for debugging only
23              
24             require 5.002;
25              
26 1     1   822 use Net::Telnet::Wrapper;
  1         23722  
  1         40  
27              
28 1     1   982 use Cisco::Accounting::Interface; ## object that represents a single interface
  1         3  
  1         41  
29 1     1   687 use Cisco::Accounting::Data; ## object that represents the parsed accounting data
  1         2  
  1         2245  
30              
31              
32              
33             sub new() {
34 0     0 1   my ($this, %parms) = @_;
35 0   0       my $class = ref($this) || $this;
36 0           my $self = {};
37            
38 0           $self->{'session'} = ''; # this will contain our session to Net::Telnet::Wrapper
39            
40 0   0       $self->{'host'} = $parms{'host'} || ""; # router to connect to
41 0   0       $self->{'user'} = $parms{'user'} || ""; # login username
42 0   0       $self->{'pwd'} = $parms{'pwd'} || ""; # login password
43 0   0       $self->{'tacacs'} = $parms{'tacacs'} || ""; # tacacs password
44 0   0       $self->{'enable_user'} = $parms{'enable_user'} || ""; # enable username
45 0   0       $self->{'enable_pwd'} = $parms{'enable_pwd'} || ""; # enable password
46 0 0         $self->{'persistent'} = (defined($parms{'persistent'}))?($parms{'persistent'}):(1); # if enabled then we don't close any sessions unless close() is called
47 0   0       $self->{'lastpoll_details'} = $parms{'lastpoll_details'} || 0;
48            
49 0           $self->{'interfaces'} = []; # array of Cisco::Accounting::Interface objects
50 0           $self->{'data'} = ''; # this will contain the output (Cisco::Accounting::Data object)
51 0           $self->{'lastpoll_data'} = '';
52            
53 0   0       $self->{'acct_type'} = $parms{'acct_type'} || "cisco"; # "cisco" ip accounting, "ipcad" is also -limited- supported
54 0 0         $self->{'keep_history'} = (defined($parms{'keep_history'}))?($parms{'keep_history'}):(1); # keep summarized historical data for each poll
55            
56 0           $self->{'telnet_options'} = $parms{'telnet_options'};
57            
58 0           bless($self, $class);
59 0           &_init($class);
60 0           return($self);
61             }
62              
63              
64             # initialization
65             sub _init {
66 0     0     my $class=shift;
67             }
68              
69              
70             ##
71             ## fetch all interfaces on a cisco device that support ip accounting
72             ## returns array of Cisco::Accounting::Interface objects
73             ## this procedured should be used with eval {}
74             ##
75             sub get_interfaces() {
76 0     0 1   my ($self) = shift;
77            
78 0           my $disconnect;
79             my @interfaces; # resulting array of Cisco::Accounting::Interface objects
80            
81 0           eval {
82 0 0         if (!$self->{'session'}) {
83 0           $disconnect = 1;
84             ## make a connection to the device
85 0           $self->_connect();
86             }
87            
88             ## get the interface information
89 0 0         if ($self->{'acct_type'} =~ /cisco/i) {
    0          
90 0           my @result = $self->{'session'}->cmd("show ip int");
91 0 0         @interfaces = &_parse_cisco_interfaces(\@result) if (scalar @result);
92             }
93             elsif ($self->{'acct_type'} =~ /ipcad/i) {
94 0           my @result = $self->{'session'}->cmd("rsh localhost stat");
95 0 0         @interfaces = &_parse_ipcad_interfaces(\@result) if (scalar @result);
96             }
97            
98             ## close the connection again
99 0 0 0       if ( ($disconnect > 0) && ($self->{'persistent'} <= 0) ) {
100 0           $self->_disconnect();
101             }
102             };
103 0 0         if ($@) {
104 0           croak $@;
105             }
106            
107 0           @{$self->{'interfaces'}} = @interfaces;
  0            
108            
109 0           return @{$self->{'interfaces'}};
  0            
110             }
111              
112              
113             ##
114             ## Disable ip accounting on one or more interfaces
115             ## parameters = array of interface id's as known in $self->{'interfaces'}
116             ## ** this assumes you've run get_interfaces first ! **
117             ## ** this assumes that you have enough rights to go to config mode **
118             ##
119             sub enable_accounting() {
120 0     0 1   my ($self) = shift;
121 0           my (@int_id) = @_;
122            
123 0           $self->_modify_accounting_settings(1, @int_id);
124             }
125              
126              
127             ##
128             ## Disable ip accounting on one or more interfaces
129             ## parameters = array of interface id's as known in $self->{'interfaces'}
130             ## ** this assumes you've run get_interfaces first ! **
131             ## ** this assumes that you have enough rights to go to config mode **
132             ##
133             sub disable_accounting() {
134 0     0 1   my ($self) = shift;
135 0           my (@int_id) = @_;
136            
137 0           $self->_modify_accounting_settings(0, @int_id);
138             }
139              
140              
141              
142             ##
143             ## parse output of 1 poll (show ip accounting) and update $self->{'data'}
144             ## returns the reference to the output
145             ## this procedure should be used with eval{}
146             ##
147             sub do_accounting() {
148 0     0 1   my ($self) = shift;
149              
150 0           my (@output);
151 0           my $disconnect = 0;
152              
153             # if the connection is not yet active then we assume that it has to be closed again
154 0 0         if (!$self->{'session'}) {
155 0           $disconnect = 1;
156 0           eval {
157 0           $self->_connect();
158             };
159 0 0         if ($@) {
160 0           croak $@;
161             }
162             }
163              
164 0           eval {
165 0 0         if ($self->{'acct_type'} =~ /cisco/i) {
    0          
166 0           @output = $self->{'session'}->cmd("show ip accounting");
167             }
168             elsif ($self->{'acct_type'} =~ /ipcad/i) {
169 0           @output = $self->{'session'}->cmd("rsh localhost show ip accounting");
170             }
171             };
172 0 0         if ($@) {
173             ## if this failed we assume that our connection was lost
174 0           $self->{'session'} = '';
175 0           croak $@;
176             }
177            
178              
179             ## create a new Cisco::Accounting::Data object if needed
180 0 0         if (!$self->{'data'}) {
181 0           $self->{'data'} = Cisco::Accounting::Data->new('keep_history' => $self->{'keep_history'});
182             }
183 0           $self->{'data'}->parse(\@output);
184              
185             ## check if we need to store lastpoll_data details
186 0 0         if ($self->{'lastpoll_details'} > 0) {
187 0           $self->{'lastpoll_data'} = Cisco::Accounting::Data->new('keep_history' => 0);
188 0           $self->{'lastpoll_data'}->parse(\@output);
189             }
190              
191 0 0 0       if ( ($disconnect > 0) && ($self->{'persistent'} <= 0) ) {
192 0           $self->_disconnect();
193             }
194            
195 0           return $self->{'data'}->get_data();
196             }
197              
198              
199             ##
200             ## returns a reference to the output
201             ##
202             sub get_output() {
203 0     0 1   my ($self) = shift;
204            
205 0 0         if ($self->{'data'}) {
206 0           return $self->{'data'}->get_data();
207             }
208             else {
209 0           return 0;
210             }
211             }
212              
213              
214             ##
215             ## returns a reference to the output
216             ##
217             sub get_lastpoll_output() {
218 0     0 1   my ($self) = shift;
219            
220 0 0         if ($self->{'lastpoll_data'}) {
221 0           return $self->{'lastpoll_data'}->get_data();
222             }
223             else {
224 0           return 0;
225             }
226             }
227              
228             ##
229             ## return reference to hash with polling statistics
230             ##
231             sub get_statistics() {
232 0     0 1   my ($self) = shift;
233            
234 0 0         if ($self->{'data'}) {
235 0           return $self->{'data'}->get_stats();
236             }
237             else {
238 0           return 0;
239             }
240             }
241              
242              
243             ##
244             ## return reference to hash with polling statistics
245             ##
246             sub get_history() {
247 0     0 1   my ($self) = shift;
248            
249 0 0         if ($self->{'data'}) {
250 0           return $self->{'data'}->get_history();
251             }
252             else {
253 0           return 0;
254             }
255             }
256              
257             ##
258             ## clears the output buffer
259             ##
260             sub clear_output() {
261 0     0 1   my ($self) = shift;
262            
263 0           $self->{'data'} = '';
264             }
265              
266              
267             ##
268             ## clears ip accounting information on the remote device
269             ## this procedure should be used with eval {}
270             ##
271             sub clear_accounting() {
272 0     0 1   my ($self) = shift;
273            
274 0           my $disconnect = 0;
275            
276             # if the connection is not yet active then we assume that it has to be closed again
277 0 0         if (!$self->{'session'}) {
278 0           $disconnect = 1;
279 0           eval {
280 0           $self->_connect();
281             };
282 0 0         if ($@) {
283 0           croak $@;
284             }
285             }
286            
287 0           eval {
288 0 0         if ($self->{'acct_type'} =~ /cisco/i) {
    0          
289 0           $self->{'session'}->cmd('clear ip accounting');
290             }
291             elsif ($self->{'acct_type'} =~ /ipcad/i) {
292 0           my @output = $self->{'session'}->cmd("rsh localhost clear ip accounting");
293 0 0         if ( grep { $_ =~ /permission denied/i } @output) {
  0            
294 0           croak "cannot clear ip accounting : permission denied";
295             }
296             }
297             };
298 0 0         if ($@) {
299 0           croak $@;
300             }
301            
302 0 0 0       if ( ($disconnect > 0) && ($self->{'persistent'} <= 0) ) {
303 0           $self->_disconnect();
304             }
305             }
306              
307              
308             ##
309             ## Send a keepalive (new line character), do not do any error checking here
310             ## Useful if 'persistent' is enabled, but still it's up to you to call the keepalive in time before session times out
311             ##
312             sub keepalive() {
313 0     0 1   my ($self) = shift;
314            
315 0 0         if ($self->{'session'}) {
316 0           eval {
317 0           $self->{'session'}->cmd(" ");
318             };
319             }
320             }
321              
322              
323              
324              
325             ### TODO: do not go to config mode unless really needed
326              
327             ##
328             ## Enable (1) or Disable (0) ip accounting depending on $status
329             ##
330             sub _modify_accounting_settings() {
331 0     0     my ($self) = shift;
332 0           my ($status) = shift;
333 0           my (@int_id) = @_;
334            
335             ## IPCAD interfaces are always enabled
336 0 0         if ($self->{'acct_type'} =~ /ipcad/i) {
337             ## nothing to do
338 0           return;
339             }
340            
341             ## first check if we need to do something
342 0 0         next unless ((scalar @int_id) > 0);
343 0 0         next unless ((scalar @{$self->{'interfaces'}}) > 0);
  0            
344              
345 0           my $disconnect = 0;
346 0           my ($id, $int);
347            
348             # if the connection is not yet active then we assume that it has to be closed again
349 0 0         if (!$self->{'session'}) {
350 0           $disconnect = 1;
351 0           eval {
352 0           $self->_connect();
353             };
354 0 0         if ($@) {
355 0           croak $@;
356             }
357             }
358            
359 0           eval {
360 0 0         if ($self->{'acct_type'} =~ /cisco/i) {
361             ## go to config mode
362 0           $self->{'session'}->config();
363 0           foreach $int (@{$self->{'interfaces'}}) {
  0            
364             ## only enable/disable ip accounting if needed
365 0 0 0       if ((grep { $int->get_id() =~ /^$_$/} @int_id) && ($int->get_accounting_status() != $status)) {
  0            
366 0           $self->{'session'}->cmd('interface '.$int->get_interface());
367 0           my $set_no = "";
368 0 0         $set_no = "no" unless ($status > 0);
369 0           $self->{'session'}->cmd("$set_no ip accounting output-packets");
370 0           $self->{'session'}->cmd('exit');
371             # change the interface status
372 0           $int->set_accounting_status($status);
373             }
374             }
375             ## quit config mode
376 0           $self->{'session'}->cmd('exit');
377             }
378             };
379 0 0         if ($@) {
380 0           croak $@;
381             }
382            
383 0 0 0       if ( ($disconnect > 0) && ($self->{'persistent'} <= 0) ) {
384 0           $self->_disconnect();
385             }
386             }
387              
388              
389             ##
390             ## open a new telnet connection, login and save session in $self->{'session'}
391             ##
392             sub _connect() {
393 0     0     my ($self) = shift;
394            
395 0           my $device_class;
396 0           my $enable = 1;
397            
398 0 0         if ($self->{'acct_type'} =~ /cisco/i) {
    0          
399 0           $device_class = "Cisco::IOS";
400 0           $enable = 1;
401             }
402             elsif ($self->{'acct_type'} =~ /ipcad/i) {
403 0           $device_class = "Unix::General";
404 0           $enable = 0;
405             }
406            
407             ## open a new connection
408 0           eval {
409 0           $self->{'session'} = Net::Telnet::Wrapper->new('device_class' => $device_class, -host => $self->{'host'}, %{$self->{'telnet_options'}});
  0            
410             };
411 0 0         if ($@) {
412 0           $self->{'session'} = '';
413 0           croak "Unable to connect to device ".$self->{'host'};
414             }
415            
416             ## login to enable mode
417 0           eval {
418 0           $self->{'session'}->login( 'name' => $self->{'user'}, 'passwd' => $self->{'pwd'}, 'Passcode' => $self->{'passcode'});
419 0 0         if ($enable) {
420 0           $self->{'session'}->enable( 'name' => $self->{'enable_user'}, 'passwd' => $self->{'enable_pwd'}, 'Passcode' => $self->{'passcode'});
421             }
422             };
423 0 0         if ($@) {
424 0           croak "Unable to login to device ".$self->{'host'};
425             }
426             }
427              
428              
429             ##
430             ## close telnet connection, remove session from $self->{'session'}
431             ##
432             sub _disconnect() {
433 0     0     my ($self) = shift;
434            
435 0 0         return unless ($self->{'session'});
436            
437 0           eval {
438 0           $self->Quit();
439             };
440            
441 0           $self->{'session'} = '';
442             }
443              
444              
445              
446             ##
447             ## fetch all interfaces on a cisco device that support ip accounting
448             ## returns array of Cisco::Accounting::Interface objects
449             ##
450             sub _parse_cisco_interfaces() {
451 0     0     my ($interfaces) = shift;
452            
453 0           my ($int);
454             my (@result);
455 0           my ($current_int);
456 0           my ($current_enabled);
457 0           my ($id) = 0;
458            
459 0           foreach $int (@{$interfaces}) {
  0            
460 0           $int =~ s/\n//;
461            
462             # new interface found, only fetch interfaces that support ip accounting
463 0 0         if ($int =~ /^(\S+)/) {
    0          
464 0           $current_int = $1;
465             }
466             elsif ($int =~ /IP output packet accounting is/i) {
467 0 0         if ($int =~ /enabled/i) {
468 0           $current_enabled = 1;
469             }
470             else {
471 0           $current_enabled = 0;
472             }
473            
474 0           my $interface = Cisco::Accounting::Interface->new($id, $current_int, $current_enabled);
475 0           push(@result, $interface);
476 0           $id++;
477 0           $current_int = "";
478 0           $current_enabled = "";
479             }
480             }
481            
482 0           return @result;
483             }
484              
485              
486              
487             ##
488             ## fetch all interfaces from a host running IPCAD
489             ## returns array of Cisco::Accounting::Interface objects
490             ##
491             sub _parse_ipcad_interfaces() {
492 0     0     my ($interfaces) = shift;
493              
494 0           my ($int);
495             my (@result);
496 0           my ($current_int);
497 0           my ($current_enabled);
498 0           my ($id) = 0;
499              
500 0           foreach $int (@{$interfaces}) {
  0            
501 0           $int =~ s/\n//;
502              
503             # new interface found, these interfaces ALWAYS have ip accounting enabled ?? TODO: need to verify this
504 0 0         if ($int =~ /Interface (.*):/i) {
505 0           $current_int = $1;
506 0           $current_enabled = 1;
507            
508 0           my $interface = Cisco::Accounting::Interface->new($id, $current_int, $current_enabled);
509 0           push (@result, $interface);
510 0           $id++;
511 0           $current_int = "";
512 0           $current_enabled = "";
513             }
514             }
515            
516 0           return @result;
517             }
518              
519             1; # End of Cisco::Accounting
520              
521              
522             __END__