ТОВА ДАЛИ СТЕ СВЪРШИ РАБОТА
#!/usr/bin/perl
#quick code by <steffen@dett.de> under Terms of GPL
# what's free it's free...
#$Revision: 1.24 $
#some kind of autodetection for chains/ipfwadm
# in chains mode, an chain "account" is used.
#ipfwadm mode is beeing dropped in future versions.
#data is stored persistent in /var/log/accountings
($sum_file)
# you should call this script often in quite mode ($0 -q)
# to keep this file uptodate. Once a day (or week or...)
you
# could call it by cron without parameters to get a mail
# (STDOUT) with the formated output
#Examples:
# build Chain "account"
#ipchains -A account -i eth0
#ipchains -A account -i ipsec0
#ipchains -A account -s 192.168.1.1
# (...)
#insert into master chain:
#ipchains -I input -j account
#ipchains -I output -j account
# use script often with "-q" (quite), and to display
invoke
it without
#paramters
#Output looks like (here with german language)
# (errors are english always)
#
# Account logger for Steffen's firewall $Revision: 1.24 $
## Stufe 1: Laden bisheriger Werte
## Stufe 2: Analysieren der Firewalldaten
## Stufe 3: Speichern der Werte
## Stufe 4: Ruecksetzen der Firewallzaehler
## Stufe 5: Ausgabe der berechten Werte
#
#------------------------------[ Hosts
]------------------------------
#
#Gerдt eth0:
# dev <--> 107.96 MB 12.65 DM
#Gerдt eth1:
# dev <--> 24.91 MB 2.92 DM
#Gerдt lo:
# dev <--> 4313.00 KB 0.49 DM
#Gerдt ippp6:
# dev <--> 2253.02 KB 0.26 DM
#Host 192.168.1.1 [www.domain.com]:
# in <--- 11.86 MB 1.39 DM
# out ---> 77.95 MB 9.14 DM
#Host anywhere []:
# in <--- 25.83 MB 3.03 DM
# out ---> 25.83 MB 3.03 DM
#Host 192.168.1.2 [mail.domain.com]:
# in <--- 7.36 KB 0.00 DM
# out ---> 41.67 KB 0.00 DM
#Host 192.168.1.3 []:
# in <--- 0.00 KB 0.00 DM
# out ---> 0.00 KB 0.00 DM
#
#------------------------------[ SUMME
]------------------------------
#
# dev <--> 141.69 MB 16.60 DM
# in <--- 37.70 MB 4.42 DM
# out ---> 103.83 MB 12.17 DM
# Gesammt <--> 283.21 MB 120.00 DM
#
#-----------------------[ Kostende Interfaces
]-----------------------
#
# eth0 <--> 107.96 MB 120.00 DM
#
use strict;
use Socket;
my $english = "1"; #set to "0" for some german output
#persistente Info in:
my $sum_file="/var/log/accountings";
my @PAY_DEVS=(" eth0"); #devs on what traffic must be payed
#z.B. auch 3 fьr Parser etc.
#engl: i.e. level 3 traces parser and so on.
# debug level below usually completly senseless, use 1 or
2...
my $DEBUG=0;
#Ausgaben unterdrьcken (fьr hourly-cron)
#engl. suppresses output if set (i.e. for cron)
my $quite = 0;
my $mode="";
my $print_mode="2";
my $TEST="no";
my $host_names="yes";
sub parse_ipfw($)
{
#parses line from ipfwadm
my $line = shift;
my @entry;
if (($line =~ /IP accounting rules/) or
($line =~ /pkts bytes dir prot/) ) {
return undef;
}
@entry = split(" ", $line); #space separated
return @entry;
}
sub parse_chains($)
{
#the chain "account" is inserted into input and output
chain
# (what means that direction is i/o)
# for use with firewall.std Rev1.25 or higher
#parses line from ipfwadm
my $line = shift;
my @entry;
if (($line =~ /^Chain account/) or
($line =~ /^ pkts bytes target prot opt/) ) {
print " Skipped: $line\n" if ($DEBUG>3);
return undef;
}
chomp $line;
$line =~ s/^\s+//; #no leading spaces
#my ($pkts, $bytestr, $target, $prot, $opt, $tosa, $tosx,
# $ifname, $mark, $outsize, $src, $dest, $port,
$junk)
# = split(/\s{1,14}/, $line); #space separated, max
14
# #char width
my ($pkts, $bytestr, $target, $prot, $opt, $tosa, $tosx,
$ifname, @rest)
= split(/\s+/, $line); #normal, non-empty space
speparateds
my ($mark, $outsize, $src, $dest, $port, $junk) ;
my $rest = join (" ", @rest); #make normal string back
if (!defined $rest or ($rest eq "")) {
print "Warning: Parse problem (no rest)!\n" if
($DEBUG);
} else {
print "Expr: '$rest'\n" if ($DEBUG>5);
#parse rest by regEx
if ($rest =~ m/^((\S+)\s+)? #possible: mark
((\S+)\s+)? #possible: outsize
(\S+)\s+ #must: source
(\S+)\s+ # dest
(\S+) # port
$/x ) {
$mark = $1;
$outsize = $3;
$src=$5;
$dest=$6;
$port=$7;
} else {
print "Warning: Parse problem (no match)!\n"
if
($DEBUG);
}
}
my $dir="i/o";
if (!defined $port or ($port eq "")) {
print "Warning: Parse problem (no port)!\n" if
($DEBUG);
}
if (defined $junk and ($junk ne "")) {
print "Warning: Parse problem (junk:$junk)!\n" if
($DEBUG);
}
if ($src eq "0.0.0.0/0") {
$src="anywhere";
}
if ($dest eq "0.0.0.0/0") {
$dest="anywhere";
}
if ( ($dest eq "anywhere") and ($src eq "anywhere") ) {
#not more than a hack currently...
$src = " $ifname";
$dir = "dev";
}
if ($DEBUG > 4) {
print " Parser: $pkts Pkts $bytestr Bytes,
Target=$target "
. "P:$prot ($opt,$tosa,$tosx) dev:$ifname,
"
. "mark/outs.:$mark/$outsize $src<-->$dest
:
$port\n";
}
return ($pkts, $bytestr, $dir, $prot, $src, $dest,
$port);
}
sub read_in($%)
{
my ($program);
my $traffic = shift;
if ( -x "/sbin/ipchains" ) {
#CHAINS mode
if ($TEST ne "yes") {
$program = "/sbin/ipchains -nvL account";
} else {
$program = "cat out2";
}
$mode = "c";
print "Chains-Mode.\n" if ($DEBUG>2);
} elsif ( -x "/sbin/ipfwadm" ) {
#IPFW mode
$program = "/sbin/ipfwadm -A -l";
$mode = "f";
print "IPFW-Mode.\n" if ($DEBUG>2);
} else {
die "No firewall program found!\n";
}
open(IPFW, "$program|") #output kommt von
or die ("Error fork/execute '$program' [$!]\n");
my ($line);
my @entry;
#damit $entry[4] lesbarer wird zu $entry[$$dir]
# eigentlich sollte $dir reichen, weil eh nicht
konstant
# richtig wдre wohl "*dir = \1" oder so, hab's
vergessen
# aber so geht's auch :)
#engl: to make it more readble: $entry[4] now we can
write
# as $entry[$$dir] (entry of direction).
# This should be $dir, but we have a ref to $dir -->
$$dir.
# to make $$dir const, it should be initialized
# with *dir = \1 or so, this is real perl magic :)
my ($pkts, $bytestr, $dir, $prot, $src, $dest, $port) =
(\0, \1, \2, \3, \4, \5, \6);
print "pkts|bytes|dir|prot|src|dest|port\n" if
($DEBUG>2);
while($line = <IPFW>) {
print "\n Raw: $line\n" if ($DEBUG>4);
if ($mode eq "f") {
@entry = parse_ipfw($line);
} elsif ($mode eq "c") {
@entry = parse_chains($line);
} else {
die "Invalid mode $mode (internal error)\n";
}
next unless defined ($entry[0]); #weiter, bis was
definiert
print join("|", @entry), "\n" if ($DEBUG>2);
#erweiterbar: hier wird nur src bzw. dest anywhere
# ausgewertet, diese Blцcke kцnnten wiederholt
werden mit
# z.B. dest mailrelay.domain.TLD oder sowas...
#currently only src and dest "anywhere" get
evaluated.
# you could extend this script here to evaluate
more
# complex rules, i.e. from host _and_ per device
or
# whatever you desire.
if ( ($entry[$$dir] eq "i/o") &&
($entry[$$prot] eq "all") &&
($entry[$$src] eq "anywhere") &&
($entry[$$port] eq "n/a" )) {
#outgoing:
my $bytes = $entry[$$bytestr];
if ($bytes =~ s/K//) {
$bytes *= 2**10;
}
if ($bytes =~ s/M//) {
$bytes *= 2**20;
}
print "IN Bytes to add: ", $bytes, "\n" if ($DEBUG>3);
$traffic->{$entry[$$dest]}->{"in"} += $bytes;
#next;
#weiter im Output (jede Zeile nur ein Eintrag)
#engl. go on in output, only one entry per line
}
if ( ($entry[$$dir] eq "i/o") &&
($entry[$$prot] eq "all") &&
($entry[$$dest] eq "anywhere") &&
($entry[$$port] eq "n/a" )) {
#outgoing:
my $bytes = $entry[$$bytestr];
if ($bytes =~ s/K//) {
$bytes *= 2**10;
}
if ($bytes =~ s/M//) {
$bytes *= 2**20;
}
print "OUT Bytes to add: ", $bytes, "\n" if ($DEBUG>3);
$traffic->{$entry[$$src]}->{"out"} += $bytes;
#next;
}
if ( ($entry[$$dir] eq "dev") &&
($entry[$$prot] eq "all") &&
($entry[$$dest] eq "anywhere") &&
($entry[$$port] eq "n/a" )) {
#Device
my $bytes = $entry[$$bytestr];
if ($bytes =~ s/K//) {
$bytes *= 2**10;
}
if ($bytes =~ s/M//) {
$bytes *= 2**20;
}
print "DEV Bytes to add: ", $bytes, "\n" if ($DEBUG>3);
$traffic->{$entry[$$src]}->{"dev"} += $bytes;
#next;
}
#wenn kein next in den if's, dann kann man auch
mehrfach
# zдhlen. MuЯ man aber nicht :)
#engl: if not next in if's, then the traffic may be
# counted multiple times depending on the rules.
# This is what most people would expect :)
}
#return $traffic;
#brauch nicht, weil ja referenz, und die ist konstant.
#engl: not neccesary, since we got a reference passed
:)
}
sub reset_acc()
{
if ($mode eq "f") {
if ( system("/sbin/ipfwadm","-A", "-z") ) {
die "Error executing ipfwadm reset! $?\n";
}
} elsif ($mode eq "c") {
if ( system("/sbin/ipchains","-Z") ) {
die "Error executing ipchains reset! $?\n";
}
} else {
die "No firewall program found!\n";
}
}
sub load($%)
{
#handles new tab separated format and old format
if ( ! -e $sum_file ) {
print "Waring: $sum_file does not exists!\n" if
($DEBUG>1);
return;
}
#sumfile-->hash_ref
my ($host, $entry, $line) = ("","",0);
my $traffic = shift;
open(ACC, "$sum_file")
or die ("Error opening accountings file
'$sum_file'
[$!]\n");
while ($entry = <ACC>) {
$line++;
print $entry, "\n" if ($DEBUG>2);
chomp $entry;
my ($host,$direction,$bytes,$rest);
if ($entry !~ /\t/) { #kein Tab drin, muЯ altes Format
sein
($host,$direction,$bytes,$rest)
= split(/ \| /,$entry);
print " Line converted to new format\n" if ($DEBUG);
} else { #neues Format (Tab sep.)
($host,$direction,$bytes,$rest)
= split(/\t/,$entry);
}
print "$host | $direction | $bytes ($rest)\n" if
($DEBUG>2);
$traffic->{$host}->{$direction} = $bytes;
if (($bytes eq "") || ($rest ne "")) {
print "Warning!! Struc-error $sum_file line $line!\n";
die "EXITING.\n";
}
}
close (ACC);
}
sub save($%)
{
#hash_ref-->sumfile
my $host;
my $traffic = shift;
open(ACC, ">$sum_file")
or die "cannot open $sum_file for writing: $! \n";
while ($host = each(%$traffic)) {
my $direction;
my $traffic_host = $traffic->{$host};
while ($direction = each(%$traffic_host)) {
my $entry=join("\t",$host, $direction,
$traffic_host->{$direction});
print ACC $entry , "\n";
}
}
close (ACC);
chmod 0600, $sum_file;
chown 0,0, $sum_file;
}
sub calc_part_costs($)
{
#Kunden
my $fee = 120; #DM/gig
my $bytes = shift;
my $Gbytes = $bytes / 2**30;
my $cost = $Gbytes * $fee;
my $cost_str = sprintf("%.2f DM", $cost);
#der Ausdruck formatiert "123456789 KB" -->
"123.456.789
KB"
while ( $cost_str =~ s/(\d+)(\d{3})(.| |$)/$1.$2$3/ ) {}
return ($cost_str);
}
sub calc_total_costs($)
{
#echt
#my $free = 2**30; #first gig free
my $free = 0; #first gig free
my $fee = 120; #DM/gig
my $bytes = shift;
if (($bytes -= $free)<0) {
return "0 DM";
}
my $Gbytes = $bytes / 2**30;
my $Gbytes_i = int($Gbytes);
if ($Gbytes_i != $Gbytes) {
$Gbytes_i++;
}
my $cost = $Gbytes_i * $fee;
my $cost_str = sprintf("%.2f DM", $cost);
while ( $cost_str =~ s/(\d+)(\d{3})(.| |$)/$1.$2$3/ ) {}
return ($cost_str);
}
sub print_val($)
{
my $byte_str = shift;
my $bytes = $byte_str;
#PB: Petabyte --> 2**50
if ($byte_str>=(2**40)) {
#Terabyte, wer weiЯ, vielleicht kommts mal :)
$byte_str /= 2**40;
$byte_str = sprintf("%9.4f TB", $byte_str);
} elsif ($byte_str>=(2**30)) {
#max 1 tera
$byte_str /= 2**30;
$byte_str = sprintf("%9.4f GB", $byte_str);
} elsif ($byte_str>=(2**20)) {
#max 1 giga
$byte_str /= 2**20;
$byte_str = sprintf("%9.4f MB", $byte_str);
} elsif ($byte_str>=(2**10)) {
#max 1 mega
$byte_str /= 2**10;
$byte_str = sprintf("%9.4f KB", $byte_str);
} else {
#max 1 kilo
$byte_str = sprintf("%9d ", $byte_str);
}
while ( $bytes =~ s/(\d+)(\d{3})(.|$)/$1.$2/ ) {}
printf("%20s == %-13s", $bytes, $byte_str);
}
sub print_val_new($)
{
my $byte_str = shift;
my $bytes = $byte_str;
#PB: Petabyte --> 2**50
if ($byte_str>=(10*2**40)) {
#Terabyte, wer weiЯ, vielleicht kommts mal :)
$byte_str /= 2**40;
$byte_str = sprintf("%9.2f TB", $byte_str);
} elsif ($byte_str>=(10*2**30)) {
#max 10 tera
$byte_str /= 2**30;
$byte_str = sprintf(" %9.2f GB", $byte_str);
} elsif ($byte_str>=(10*2**20)) {
#max 10 giga
$byte_str /= 2**20;
$byte_str = sprintf(" %9.2f MB", $byte_str);
} else {
$byte_str /= 2**10;
$byte_str = sprintf(" %9.2f KB", $byte_str);
}
while ( $bytes =~ s/(\d+)(\d{3})(.|$)/$1.$2/ ) {}
printf("%-26s", $byte_str);
}
sub print_dir($)
{
my $direction = shift;
printf(" %-6s", $direction);
if ($direction eq "in") {
printf(" <--- ");
} elsif ($direction eq "out") {
printf(" ---> ");
} elsif ($direction eq "dev") {
printf(" <--> ");
} else {
printf(" ?--? "); #gibts erstmal nicht, kommt vielleicht
#noch fьr andere Ziele...
}
}
sub print_traffic($%)
{
print "\n", ("-" x 30), "[ Hosts ]", ("-" x 30), "\n\n";
my $traffic = shift;
my ($host) = ("");
my $total = {};
sub by_traffic()
{
my ($traf_a,$traf_b) = (0,0);
#print keys %{$traffic->{$b}};
#jedes device gewinnt gegen jeden host
#engl: devices have higher sort priority than hosts
if (defined($traffic->{$a}->{'dev'}) and
!defined($traffic->{$b}->{'dev'})) {
return -1;
}
#jeder host verliert gegen jedes device
#engl: hosts have lower sort priority than devices
if (defined($traffic->{$b}->{'dev'}) and
!defined($traffic->{$a}->{'dev'})) {
return 1;
}
#devices oder hosts untereinander nach traffic
sortieren
#engl. devices and hosts are sorted by traffic
foreach my $direction (keys %{$traffic->{$a}}) {
$traf_a += $traffic->{$a}->{$direction};
}
foreach my $direction (keys %{$traffic->{$b}}) {
$traf_b += $traffic->{$b}->{$direction};
}
return $traf_b <=> $traf_a;
}
foreach $host (sort by_traffic (keys %$traffic)) {
if (!defined($traffic->{$host}->{'dev'})) {
if ($host_names eq "yes") {
printf("Host %s [%s]:\n", $host,
gethostbyaddr(inet_aton($host), AF_INET));
} else {
printf("Host %s:\n", $host);
}
} else {
if ($english) {
printf("Device %s:\n", $host);
} else {
printf("Gerдt %s:\n", $host);
}
}
my $direction;
my $traffic_host = $traffic->{$host};
foreach $direction (sort (keys %$traffic_host)) {
print_dir($direction);
my $bytes = $traffic_host->{$direction};
$total->{$direction} += $bytes;
if ($print_mode eq "c") {
print_val($bytes); #classic
} else {
print_val_new($bytes); #differs a little
}
printf("%15s\n", calc_part_costs($bytes));
}
#print "\n";
}
#
# SUMME (sumarized data)
#
if ($english) {
print "\n", ("-" x 31), "[ SUM ]", ("-" x 31), "\n\n";
} else {
print "\n", ("-" x 30), "[ SUMME ]", ("-" x 30),
"\n\n";
}
my ($direction, $all) = ("", 0);
foreach $direction (sort(keys %$total)) {
print_dir($direction);
my $bytes = $total->{$direction};
if ($print_mode eq "c") {
print_val($bytes); #classic
} else {
print_val_new($bytes); #differs a little
}
printf("%15s\n", calc_part_costs($bytes));
$all += $bytes;
}
if ($english) {
print " *** SUM <--> "; #engl: "sumarzied"
} else {
print " Gesammt <--> "; #engl: "sumarzied"
}
if ($print_mode eq "c") {
print_val($all); #classic
} else {
print_val_new($all); #differs a little
}
printf("%15s\n", calc_total_costs($all));
if ($english) {
print "\n", ("-" x 24), "[ Expensive Devices ]", ("-"
x
24), "\n\n";
} else {
print "\n", ("-" x 23), "[ Kostende Interfaces ]",
("-"
x 23), "\n\n";
}
foreach my $PAY_DEV (sort(@PAY_DEVS)) {
my $traffic_host = $traffic->{"$PAY_DEV"};
my $bytes=0;
foreach $direction (sort(keys %$traffic_host)) {
$bytes += $traffic_host->{$direction};
}
printf(" %-6s <--> ", $PAY_DEV);
if ($print_mode eq "c") {
print_val($bytes); #classic
} else {
print_val_new($bytes); #differs a little
}
printf("%15s\n", calc_total_costs($bytes));
}
print "\n";
}
sub get_switches
{
foreach (@ARGV)
{
if (/^-(D+)$/) {
$DEBUG += length ($1)
}
if (/^-q$/) {
$quite = "true";
}
}
}#end sub get_schwitches
################
#
# MAIN
#
################################
my @stage;
if ($english) {
$stage[1] = "# Stage 1: Load stored values from
disk\n"
;
$stage[2] = "# Stage 2: Analyzing data got from
firewall\n";
$stage[3] = "# Stage 3: Storeing the values\n";
$stage[4] = "# Stage 4: Reseting firewall traffic
counters\n";
$stage[5] = "# Stage 5: Output of the calculated
values\n";
} else {
$stage[1] = "# Stufe 1: Laden bisheriger Werte\n" ;
$stage[2] = "# Stufe 2: Analysieren der
Firewalldaten\n";
$stage[3] = "# Stufe 3: Speichern der Werte\n";
$stage[4] = "# Stufe 4: Ruecksetzen der
Firewallzaehler\n";
$stage[5] = "# Stufe 5: Ausgabe der berechten
Werte\n";
}
get_switches;
print ("Debug-Level: ",$DEBUG, "\n") if ($DEBUG);
my $traffic = {};
print ' Account logger for Steffen\'s firewall $Revision:
1.24 $',"\n" unless $quite;
#in english: Stage 1: Load saved values
print $stage[1] unless $quite;
load($traffic);
#in english: Stage 2: analyzing the firewall data
print $stage[2] unless $quite;
read_in($traffic);
#in english: Stage 3: Storeing the values
print $stage[3] unless $quite;
save($traffic) unless $DEBUG;
#in english: Stage 4: Reset of firewall counters
print $stage[4] unless $quite;
reset_acc() unless $DEBUG;
#in english: Stage 5: Output of calculated values
print $stage[5] unless $quite;
#exit if $quite; # besser?!
print_traffic($traffic) unless $quite;
print "\n" unless $quite;
|