Blame contrib/whodo/collect.pl

Packit 667938
# collect.pl
Packit 667938
#
Packit 667938
#	Collect IP accounting data from a cisco router and summarise it
Packit 667938
#	into a CSV file. The file shows number of bytes sent from each
Packit 667938
#	source device to each destination network.
Packit 667938
#	Optionally, the data is also summarised by source (all destinations added
Packit 667938
#	together) and written out to an mrtg config file.
Packit 667938
#	There are also two input files. "networks" is used to map network
Packit 667938
#	addresses to names. "sources" is optional & is used to map source
Packit 667938
#	addresses to host names.
Packit 667938
#
Packit 667938
# Args are:
Packit 667938
#	h	help
Packit 667938
#	a	path of mrtg config file for data by source
Packit 667938
#	n	path of a "networks" file used to map network addresses to names
Packit 667938
#	o	path of output CSV file. Defaults to YYYYMMDD-HHMMSS.CSV
Packit 667938
#	s	path of a "sources" file mapping source addresses to host names
Packit 667938
#
Packit 667938
#	v1.0	17/9/98		Tony Farr	Original
Packit 667938
#	v1.1	16/12/98	Tony Farr	Add horrid kludge to collect Exchange traffic
Packit 667938
#									by source
Packit 667938
#	v1.2	22/3/99		Tony Farr	Tidy up
Packit 667938
#	v1.3	25/3/99		Tony Farr	Remove v1.1 kludge
Packit 667938
#									Just generate one mrtg line/data set
Packit 667938
#
Packit 667938

Packit 667938
use SNMP_util;
Packit 667938
use Getopt::Std;
Packit 667938
use File::Basename;
Packit 667938
use strict;
Packit 667938
use Socket;
Packit 667938

Packit 667938
use vars qw/$opt_h $opt_a $opt_n $opt_o $opt_s/;
Packit 667938

Packit 667938
#	CONSTANTS
Packit 667938
# Note inclusion of the write community string
Packit 667938
my $HOST= '0ztrad3@canb-wan';
Packit 667938
# Directory for output logs/csv files.
Packit 667938
my $LOGPATH= "D:\\logs\\whodo\\";
Packit 667938
# Directory for MRTG config files for traffic sources
Packit 667938
my $SOURCEDIR= "D:\\www\\mrtg\\whodo";
Packit 667938
# Any source generating more than BIGBYTES per poll will be added to the sources config file automatically
Packit 667938
my $BIGBYTES= 40000000;
Packit 667938

Packit 667938
my(@dstdesc, @dstaddr, @dstmask, @srcaddr, @srcdesc, %traffictab);
Packit 667938
my $progname = basename($0);
Packit 667938
my $usage = "Usage: $progname [-h] [-a mrtg_config_file] [-n network_file] [-o output_file] [-s source_address_file]\n";
Packit 667938
# Parse Command Line:
Packit 667938
die $usage unless getopts('ha:n:o:s:');
Packit 667938
if ( defined($opt_h) ) {
Packit 667938
	print $usage;
Packit 667938
	exit(0);
Packit 667938
}
Packit 667938
if ( defined($opt_n) ) {
Packit 667938
	load_nets($opt_n);
Packit 667938
}
Packit 667938
unless ( defined($opt_o) ) {
Packit 667938
	my ($sec,$min,$hour,$mday,$month,$year) = localtime;
Packit 667938
	$opt_o= $LOGPATH . sprintf("%d%02d%02d-%02d%02d%02d.csv", $year+1900,++$month,$mday,$hour,$min,$sec);
Packit 667938
}
Packit 667938
if ( defined($opt_s) ) {
Packit 667938
	load_sources($opt_s);
Packit 667938
}
Packit 667938
my $age= checkpoint_stats($HOST);
Packit 667938
get_stats($HOST);
Packit 667938
print_stats($opt_o);
Packit 667938
if ( defined($opt_a) ) {
Packit 667938
	make_sources_config($opt_a);
Packit 667938
}
Packit 667938
exit(0);
Packit 667938

Packit 667938

Packit 667938

Packit 667938
sub load_nets {
Packit 667938
# Loads 3 arrays:
Packit 667938
#	@dstaddr - a list of IP addresses
Packit 667938
#	@dstdesc - a list of the corresponding descriptions
Packit 667938
#	@dstmask - a list of the corresponding bitmasks
Packit 667938
# The tables are loaded from a list of filenames passed in @_. That file
Packit 667938
# can be a networks file. The file(s) should have format:
Packit 667938
#		netdescription	10.10.10	/25
Packit 667938
# The "/25" is the mask & may be preceeded by a "#". It is optional.
Packit 667938
	my(@flist)= @_;
Packit 667938
	my ($desc, $end, $masksz);
Packit 667938
	while (my $fname= shift(@flist)) {
Packit 667938
		open(NETWORKS, "<$fname") || warn "$progname: unable to open $fname; $!";
Packit 667938
		while (<NETWORKS>) {
Packit 667938
			# Process #includes after dealing with the current file
Packit 667938
			if ( /^#\s*include\s+(\S+)/ ) {
Packit 667938
				push(@flist, $1);
Packit 667938
				next;
Packit 667938
			}
Packit 667938
			if ( /^\s*(\w+)\s+(\S+)(.*)/ ) {
Packit 667938
				$desc= $1;
Packit 667938
				$_= $2;
Packit 667938
				$end= $3;
Packit 667938
				push(@dstdesc, $desc);
Packit 667938
				my $octets = 1 + tr/././;
Packit 667938
				$_ .= ".0" x (4 - $octets);
Packit 667938
				push( @dstaddr, aton($_) );
Packit 667938
				if ( $end =~ /\/(\d+)/ ) {
Packit 667938
					$masksz= $1;
Packit 667938
				} else {
Packit 667938
					$masksz= $octets * 8;
Packit 667938
				}
Packit 667938
				push( @dstmask, pack("B32", "1" x $masksz . "0" x 32) );
Packit 667938
			}
Packit 667938
		}
Packit 667938
		close(NETWORKS);
Packit 667938
	}
Packit 667938
}
Packit 667938

Packit 667938

Packit 667938

Packit 667938
sub load_sources {
Packit 667938
# Loads a pair of arrays: @srcaddr (a list of IP addresses) and @srcdesc
Packit 667938
# (a list of the corresponding descriptions). The files are loaded from
Packit 667938
# a list of filenames passed in @_. That file can be a hosts file. However "address" entries
Packit 667938
# can be perl regular exprs as well literal addresses. For compatibility,
Packit 667938
# "." when used as a wild card must be expressed "\." - i.e. the reverse of normal.
Packit 667938
	my(@flist)= @_;
Packit 667938
	while (my $fname= shift(@flist)) {
Packit 667938
		open(HOSTS, "<$fname") || warn "$progname: unable to open $fname; $!";
Packit 667938
		while (<HOSTS>) {
Packit 667938
			# Process #includes after dealing with the current file
Packit 667938
			if ( /^#\s*include\s+(\S+)/ ) {
Packit 667938
				push(@flist, $1);
Packit 667938
				next;
Packit 667938
			}
Packit 667938
			($_)= split(/#/);
Packit 667938
			if ( /(\S+)\s+(\S+)/ ) {
Packit 667938
				push(@srcdesc, $2);
Packit 667938
				$_= $1;
Packit 667938
				s/\./\\\./g;			# Replace "." with "\."
Packit 667938
				s/\\\\\././g;	# If we now have "\\.", replace with "."
Packit 667938
				push(@srcaddr, $_);
Packit 667938
			}
Packit 667938
		}
Packit 667938
		close(HOSTS);
Packit 667938
	}
Packit 667938
}
Packit 667938

Packit 667938

Packit 667938

Packit 667938
sub checkpoint_stats {
Packit 667938
# Take a checkpoint on IP accounting on the given router & return the duration of it.
Packit 667938
# The checkpoint is done by doing a get  then a set on actCheckPoint
Packit 667938
	my ($age);
Packit 667938

Packit 667938
	# Find how long since the last checkpoint
Packit 667938
	($age) = snmpget ($_[0], '1.3.6.1.4.1.9.2.4.8.0');
Packit 667938
	warn "$progname: No actAge returned.\n" unless $age;
Packit 667938

Packit 667938
	# Check to see if we've lost any data
Packit 667938
	($_) = snmpget ($_[0], '1.3.6.1.4.1.9.2.4.6.0');
Packit 667938
	warn "$progname: Accounting table overflow - $_ bytes lost.\n" if $_ > 0;
Packit 667938

Packit 667938
	# Do a new checkpoint
Packit 667938
	($_) = snmpget ($_[0], '1.3.6.1.4.1.9.2.4.11.0');
Packit 667938
	die "$progname: No actCheckPoint returned.\n" unless defined;
Packit 667938
	snmpset ($_[0], '1.3.6.1.4.1.9.2.4.11.0', 'integer', $_);
Packit 667938
	$age;
Packit 667938
}
Packit 667938

Packit 667938

Packit 667938

Packit 667938
sub get_stats {
Packit 667938
# Summarise the checkpoint by destination network (not host).
Packit 667938
# Summary is placed into %traffictab - a hash of hashes indexed by
Packit 667938
# source device & destination network.
Packit 667938
	my($src, $dstnet);
Packit 667938
	my @response = snmpwalk ($_[0], '1.3.6.1.4.1.9.2.4.9.1.4' );
Packit 667938
	foreach $_ (@response) {
Packit 667938
		/(\d+\.\d+\.\d+\.\d+)\.(\d+\.\d+\.\d+\.\d+):(\d+)/ ||
Packit 667938
				die "$progname: Cannot parse response from walk.\n";
Packit 667938
		$dstnet= addr_to_net($2);
Packit 667938
		$src= addr_to_src($1);
Packit 667938
		$traffictab{$src}{$dstnet} += $3;
Packit 667938
	}
Packit 667938
}
Packit 667938

Packit 667938

Packit 667938

Packit 667938
sub print_stats {
Packit 667938
# Print out the traffictab in csv format
Packit 667938
	my ($sec,$min,$hour,$mday,$month,$year) = localtime(time());
Packit 667938
	$year += 1900;
Packit 667938
	$month++;
Packit 667938
	open (CSVFILE,">$_[0]") || die "$progname: Could not open file $_[0]; $!\n";
Packit 667938
	printf CSVFILE "End Time:,%d/%02d/%d %d:%02d:%02d\n",$mday,$month,$year,$hour,$min,$sec;
Packit 667938
	print CSVFILE "Duration:,$age\n";		# Bug alert. This breaks if $age > 1 day
Packit 667938
	my($s, $d);
Packit 667938
	foreach $s (sort keys %traffictab) {
Packit 667938
		foreach $d (sort keys %{$traffictab{$s}}) {
Packit 667938
			print CSVFILE "$s,$d,$traffictab{$s}{$d}\n";
Packit 667938
		}
Packit 667938
	}
Packit 667938
	close(CSVFILE);
Packit 667938
}
Packit 667938

Packit 667938

Packit 667938

Packit 667938
sub make_sources_config {
Packit 667938
# Print out an mrtg config file
Packit 667938
	my($cfgfile)= @_;
Packit 667938
	my(%cfgentries, $src, $dst, $t, $misc);
Packit 667938
	# Load current cfg entries
Packit 667938
	if ( open(CFG, "<$cfgfile") ) {
Packit 667938
		while (<CFG>) {
Packit 667938
			if ( /^\s*Target\[([^\]]*)/ && $1 ne "Miscellaneous" ) {
Packit 667938
				$cfgentries{ uc($1) }= 1;
Packit 667938
			}
Packit 667938
		}
Packit 667938
		close(CFG);
Packit 667938
	}
Packit 667938
	# Write out the header of a new config file
Packit 667938
	open(CFG,">$cfgfile") || die "$progname: Could not open file $cfgfile; $!\n";
Packit 667938
	write_sources_header();
Packit 667938
	# For each traffictab entry, if it's large or there's an existing CFG entry, write out a new CFG entry
Packit 667938
	foreach $src (keys %traffictab) {
Packit 667938
		$t= 0;
Packit 667938
		foreach $dst (keys %{$traffictab{$src}}) {
Packit 667938
			$t += $traffictab{$src}{$dst};
Packit 667938
		}
Packit 667938
		if ( $cfgentries{ uc($src) } ) {
Packit 667938
			delete $cfgentries{ uc($src) };
Packit 667938
			write_source_entry($src, $t);
Packit 667938
		} elsif ( $t > $BIGBYTES ) {
Packit 667938
			write_source_entry($src, $t);
Packit 667938
		} else {
Packit 667938
			$misc += $t;
Packit 667938
		}
Packit 667938
	}
Packit 667938
	# Write out new entries for any CFG entries that existed previously but we've
Packit 667938
	# missed because they generated no traffic this time.
Packit 667938
	foreach $src (keys %cfgentries) {
Packit 667938
		write_source_entry($src, 0);
Packit 667938
	}
Packit 667938
	# Write an entry for the miscellaneous odds & ends
Packit 667938
	write_source_entry("Miscellaneous", $misc);
Packit 667938
	close(CFG);
Packit 667938
}
Packit 667938

Packit 667938

Packit 667938

Packit 667938
sub write_sources_header {
Packit 667938
	print CFG <
Packit 667938
WorkDir: $SOURCEDIR
Packit 667938
IconDir: /mrtg/
Packit 667938
Interval: 30
Packit 667938

Packit 667938
END_OF_HEADER
Packit 667938
}
Packit 667938

Packit 667938

Packit 667938

Packit 667938
sub write_source_entry {
Packit 667938
	print CFG <
Packit 667938

Packit 667938
Title[$_[0]]: Traffic from $_[0]
Packit 667938
PageTop[$_[0]]: 

Traffic from $_[0]

Packit 667938
MaxBytes[$_[0]]: 12500000
Packit 667938
Options[$_[0]]: growright, bits, absolute, nopercent
Packit 667938
Colours[$_[0]]: w#ffffff,blue#0000e0,w#ffffff,r#ff0000
Packit 667938
Target[$_[0]]: `perl -e "print \\"0\\n$_[1]\\""`
Packit 667938
YLegend[$_[0]]: Bits per Second
Packit 667938
ShortLegend[$_[0]]: bps
Packit 667938
Legend1[$_[0]]:
Packit 667938
Legend2[$_[0]]: Traffic from $_[0]
Packit 667938
LegendI[$_[0]]:
Packit 667938
LegendO[$_[0]]:  Traffic:
Packit 667938

Packit 667938
END_OF_ENTRY
Packit 667938
}
Packit 667938

Packit 667938

Packit 667938

Packit 667938
sub addr_to_net {
Packit 667938
# Returns the name/description of the network of the given address.
Packit 667938
# Addresses are looked up in @dstaddr first. If that fails, the address is returned.
Packit 667938
	my($i, $dst);
Packit 667938
	$dst= aton($_[0]);
Packit 667938
	for ($i=0; $i < @dstaddr; $i++) {
Packit 667938
		if ( ($dst & $dstmask[$i]) eq $dstaddr[$i] ) {
Packit 667938
			return $dstdesc[$i];
Packit 667938
		}
Packit 667938
	}
Packit 667938
	$_[0] =~ /(.*)\..*/;		# Assume Class C & strip off the last octet
Packit 667938
	$1;
Packit 667938
}
Packit 667938

Packit 667938

Packit 667938

Packit 667938
sub BEGIN {
Packit 667938
	my ($lastaddr, $lastsrc);
Packit 667938
sub addr_to_src {
Packit 667938
# Returns the name/description of the given address.
Packit 667938
# Addresses are looked up in @srcaddr first. If there's no match, a dns lookup is tried.
Packit 667938
# If that fails, the address is returned.
Packit 667938
	if ( $_[0] eq $lastaddr ) {
Packit 667938
		return $lastsrc;
Packit 667938
	} else {
Packit 667938
		$lastaddr= $_[0];
Packit 667938
		for (my $i=0; $i < @srcaddr; $i++) {
Packit 667938
			if ($_[0] =~ /^$srcaddr[$i]$/ ) {
Packit 667938
				$lastsrc= $srcdesc[$i];
Packit 667938
				return $lastsrc;
Packit 667938
			}
Packit 667938
		}
Packit 667938
		my $addr= aton($_[0]);
Packit 667938
		if ($lastsrc= gethostbyaddr($addr, AF_INET)) {
Packit 667938
			$lastsrc =~ s/\.austrade\.gov\.au$//i;
Packit 667938
		} else {
Packit 667938
			$lastsrc= $_[0];
Packit 667938
		}
Packit 667938
		return $lastsrc;
Packit 667938
	}
Packit 667938
}
Packit 667938
}
Packit 667938

Packit 667938

Packit 667938

Packit 667938
sub aton {
Packit 667938
# I found the standard "inet_aton" very slow (on Windows).
Packit 667938
# Hence this version. It only handles dotted decimal addresses -
Packit 667938
# not names.
Packit 667938
	$_[0] =~ /(\d+).(\d+).(\d+).(\d+)/;
Packit 667938
	chr($1).chr($2).chr($3).chr($4);
Packit 667938
}