]> git.ipfire.org Git - ipfire-2.x.git/blame - config/cfgroot/net-traffic-lib.pl
Hinzugefuegt:
[ipfire-2.x.git] / config / cfgroot / net-traffic-lib.pl
CommitLineData
10a04d70
MT
1#!/usr/bin/perl
2#
3# $Id: net-traffic-lib.pl,v 1.4 2005/03/17 11:43:55 dotzball Exp $
4#
5# Summarize all IP accounting files from start to end time
6#
7# Copyright (C) 1997 - 2000 Moritz Both
8# 2001 - 2002 Al Zaharov
9#
10# This program is free software; you can redistribute it and/or modify
11# it under the terms of the GNU General Public License as published by
12# the Free Software Foundation; either version 2 of the License, or
13# (at your option) any later version.
14#
15# This program is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License
21# along with this program; if not, write to the Free Software
22# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23#
24# The author can be reached via email: moritz@daneben.de, or by
25# snail mail: Moritz Both, Im Moore 26, 30167 Hannover,
26# Germany. Phone: +49-511-1610129
27#
28#
29# 22 June 2004 By Achim Weber dotzball@users.sourceforge.net
30# - changed to use it with Net-Traffic Addon
31# - renamed to avoid issues when calling this file or original ipacsum
32# - this file is net-traffic-lib.pl for IPCop 1.4.0
33#
34
35package Traffic;
36
37use 5.000;
38use Getopt::Long;
39use POSIX qw(strftime);
40use Time::Local;
41use Socket;
42use IO::Handle;
43
44$|=1; # line buffering
45
46@moff = (0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 );
47
48# =()<$datdelim="@<DATDELIM>@";>()=
49$datdelim="#-#-#-#-#";
50# =()<$prefix="@<prefix>@";>()=
51$prefix="/usr";
52# =()<$exec_prefix="@<exec_prefix>@";>()=
53$exec_prefix="${prefix}";
54# =()<$INSTALLPATH="@<INSTALLPATH>@";>()=
55$INSTALLPATH="${exec_prefix}/sbin";
56$datdir="/var/log/ip-acct";
57
58$me=$0;
59$me =~ s|^.*/([^/]+)$|$1|;
60$now = time;
61$fetchipac="$INSTALLPATH/fetchipac";
62$rule_regex = ".*"; # match rules with this regex only
63
64## Net-Traffic variables ##
65my %allDays = ();
66my $allDaysBytes;
67my $tzoffset = 0;
68my $displayMode = "daily";
69my ($curMonth, $curYear);
70${Traffic::blue_in} = 'incoming BLUE';
71${Traffic::green_in} = 'incoming GREEN';
72${Traffic::orange_in} = 'incoming ORANGE';
73${Traffic::red_in} = 'incoming RED';
74${Traffic::blue_out} = 'outgoing BLUE';
75${Traffic::green_out} = 'outgoing GREEN';
76${Traffic::orange_out} = 'outgoing ORANGE';
77${Traffic::red_out} = 'outgoing RED';
78
79
80sub calcTraffic{
81 $allDaysBytes = shift;
82 $starttime = shift;
83 $endtime = shift;
84 $displayMode = shift;
85
86 $starttime =~ /^(\d\d\d\d)(\d\d)/;
87 $curYear = $1;
88 $curMonth = $2;
89
90 # calculate time zone offset in seconds - use difference of output of date
91 # command and time function, round it
92 $tzoffset = time-timegm(localtime());
93 $machine_name = undef;
94
95 $starttime = makeunixtime($starttime);
96 $endtime = makeunixtime($endtime);
97 $endtime -= 1;
98
99 # options that we need to pass to fetchipac if we call it.
100 $fetchipac_options = "--directory=$datdir";
101
102 $endtime = $now if ($endtime > $now);
103 $starttime = 0 if ($starttime < 0);
104 $mystarttime = &makemydailytime($starttime);
105 $myendtime = &makemydailytime($endtime);
106 %rule_firstfile = %rule_lastfile = ( );
107
108 # find out which timestamps we need to read.
109 # remember newest timestamp before starttime so we know when data for
110 # the first file starts
111 # also remember oldest timestamp after end time
112 $newest_timestamp_before_starttime = "";
113 $oldest_timestamp_after_endtime = "";
114 open(DATA, "$fetchipac $fetchipac_options --timestamps=$starttime,$endtime ".
115 "--machine-output-format|") || die "$me: cant run $fetchipac\n";
116 # the first thing is the timestamp count
117 $count=<DATA>;
118 if ($count == 0) {
119 return ();
120 }
121 while(<DATA>)
122 {
123 if (/^(.)\s(\d+)$/) {
124 my $ts = $2;
125 if ($1 eq "-") {
126 $newest_timestamp_before_starttime=$ts;
127 }
128 elsif ($1 eq "+") {
129 $oldest_timestamp_after_endtime=$ts;
130 }
131 elsif ($1 eq "*") {
132 push(@timestamps, $ts);
133 }
134 else {
135 die "$me: illegal output from $fetchipac: \"$_\"\n";
136 }
137 }
138 else {
139 die "$me: illegal output from $fetchipac: \"$_\"\n";
140 }
141 }
142 close DATA;
143
144 push(@timestamps, $oldest_timestamp_after_endtime)
145 if ($oldest_timestamp_after_endtime);
146 unshift(@timestamps, $newest_timestamp_before_starttime)
147 if ($newest_timestamp_before_starttime);
148
149 $rulenumber = 0;
150
151 # read all data we need and put the data into memory.
152 &read_data;
153
154 @days_sorted = sort keys %allDays;
155 return @days_sorted;
156}
157##########################
158# END OF MAIN PROGRAM
159##########################
160
161# read all data (@timestmaps contains the timestamps, must be sorted!)
162# and put the data into our global memory data
163# structures. special care must be taken with data of the first and
164# the last timestamps we read, since we only want data which is from our
165# time frame. Furthermore, data from before and after this time frame
166# must be preserved in special data structures because we might replace
167# them (option --replace) and have to write extra data for these times
168# then.
169sub read_data {
170 my $run_s;
171 my $s;
172 my $i;
173 my $in_time = 0;
174 my $after_time = 0;
175
176 my $curDay = $starttime;
177
178 # feed the timestamp list to fetchipac on its stdin.
179 socketpair(CHILD, PARENT, AF_UNIX, SOCK_STREAM, PF_UNSPEC)
180 or die "socketpair: $!";
181 CHILD->autoflush(1);
182 PARENT->autoflush(1);
183 my $pid = open(CHILD, "-|");
184 die "$me: can't fork: $!\n" unless defined $pid;
185 if ($pid == 0) {
186 # child
187 close CHILD;
188 open(FETCHIPAC, "|$fetchipac $fetchipac_options --record "
189 ."--machine-output-format")
190 or die "$me: cant exec fetchipac\n";
191
192#this is much more efficient than the original code (Manfred Weihs)
193# and it adds more troubles than solves (Al Zakharov)
194 if ($timestamps[0] == $newest_timestamp_before_starttime) {
195 print(FETCHIPAC $timestamps[1],"-",$timestamps[$count],"\n");
196 } else {
197 print(FETCHIPAC $timestamps[0],"-",$timestamps[$count-1],"\n");
198 }
199 close(FETCHIPAC);
200 close(PARENT);
201 exit;
202 }
203 close PARENT;
204
205 my $laststamp = undef;
206 $laststamp = $newest_timestamp_before_starttime
207 if ($newest_timestamp_before_starttime);
208 $i = 0;
209 $i++ if ($laststamp);
210 while (<CHILD>) {
211 # first line of fetchipac output: "ADD"
212 /^ADD\s*$/i or die "$me: bad line from fetchipac: $_\n";
213 # second line of fetchipac output: timestamp no_of_records
214 $_ = <CHILD> || last;
215 /^(\d+)\s(\d+)$/ or die "$me: bad line from fetchipac: $_\n";
216 my $timestamp = int $1;
217 my $number_of_records = int $2;
218 my $do_collect = 1;
219
220 if ($displayMode =~ /^daily/) {
221 # increment Day aslong current timestamp is not in current Day
222 while ( ($timestamp-$curDay) > 86399) {
223 $curDay += 86400;
224 }
225 }
226 else
227 {
228 my @dummy = localtime($timestamp);
229 # increment Month aslong current timestamp is not in current Month
230 while ($curMonth < ($dummy[4]+1) || $curYear<($dummy[5]+1900)) {
231 $curMonth++;
232 if ($curMonth > 12) {
233 $curMonth = 1;
234 $curYear++;
235 }
236 my $newMonth = $curYear;
237 $newMonth .= $curMonth < 10 ? "0".$curMonth."01" : $curMonth."01";
238 $newMonth .= "01";
239 $curDay = &makeunixtime($newMonth);
240 }
241 }
242
243 if ($timestamp < $starttime) {
244 # this record is too old, we dont need the data.
245 # However, the timestamp gives us a clue on the
246 # time period the next item covers.
247 $do_collect = 0;
248 }
249
250 my $irec;
251 # read each record
252 my $data = &read_data_record(CHILD, $number_of_records);
253
254 if ($do_collect && $in_time == 0) {
255 # the data is from after starttime. if it is the
256 # first one, split the data (if we know for how
257 # long this data is valid, and if $laststamp is not
258 # equal to $starttime in which case the split is
259 # redundant). If we don't have a clue about the
260 # last file time before our first file was created,
261 # we do not know how much of the file data is in our
262 # time frame. we assume everything belongs to us.
263 $in_time = 1;
264# if ($laststamp && $laststamp != $starttime) {
265 if ($laststamp && $laststamp != $newest_timestamp_before_starttime) {
266 my $newdata = &split_data($data,
267 $laststamp, $timestamp, $starttime);
268 $glb_data_before = $data;
269 $data = $newdata;
270 $laststamp = $starttime;
271 }
272 }
273
274 if ($timestamp > $endtime) {
275 # this data is too new, but the data in it may have
276 # begun within our time frame. (if endtime eq laststamp
277 # we do a redundant split here, too - it works for now
278 # and --replace relies on it, but it is ugly.)
279 if ($after_time == 0) {
280 $after_time = 1;
281 if ($laststamp) {
282 $glb_data_after =
283 &split_data($data,$laststamp,$timestamp,$endtime);
284 } else {
285 $do_collect = 0;
286 }
287 } else {
288 $do_collect = 0; # just too new.
289 }
290 }
291
292 if ($do_collect) {
293 &collect_data($data, $i, $curDay);
294 }
295 $laststamp = $timestamp;
296 $i++;
297 }
298 close CHILD;
299 wait;
300}
301
302# split the data in $1 (format as from read_data) into a pair of two
303# such data sets. The set referenced to as $1 will afterwards contain
304# the first part of the data, another set which is returned contains
305# the second part of the data.
306# interpret the data as having start time=$2 and end time=$3 and split
307# time=$4
308sub split_data {
309 my $data = shift;
310 my $mstart = shift;
311 my $mend = shift;
312 my $msplit = shift;
313
314 # calculate factors for multiplications
315 my $ust = $mstart;
316 my $uperiod = $mend - $ust;
317 my $usplit = $msplit - $ust;
318
319 if ($uperiod < 0) {
320 # hmmm? die Daten sind rueckwaerts???
321 $uperiod = -$uperiod;
322 }
323 my $fac1;
324 if ($usplit < 0) {
325 $fac1 = 0;
326 }
327 elsif ($usplit > $uperiod) {
328 $fac1 = 1;
329 }
330 else {
331 $fac1 = $usplit / $uperiod;
332 }
333
334 # $fac1 now says us how much weight the first result has.
335 # initialize the set we will return.
336 my @ret = ( );
337
338 foreach $set (@$data) {
339 my ($rule, $bytes, $pkts) = @$set;
340 $$set[1] = int($bytes * $fac1 + 0.5);
341 $$set[2] = int($pkts * $fac1 + 0.5);
342 push(@ret, [ $rule, $bytes - $$set[1], $pkts - $$set[2] ]);
343 }
344 return \@ret;
345}
346
347# put data from one file into global data structures
348# must be called in correct sorted file name order to set rules_lastfile
349# and rules_firstfile (which are currently useless)
350# arguments:
351# $1=index number of file; $2 = reference to array with data from file
352sub collect_data {
353 my($filedata, $ifile, $i, $day);
354
355 $filedata = shift;
356 $ifile=shift;
357 $day =shift;
358
359 # if day first appeared in this file, initialize its
360 # life.
361 if (!defined($allDays{$day})) {
362 return if (&init_filter_id($day));
363 $allDays{$day} = $rulenumber++;
364 }
365
366 for ($i=0; $i<=$#$filedata; $i++) {
367 my $set = $$filedata[$i];
368 my $rule = $$set[0];
369 my $bytes = $$set[1];
370 my $pkts = $$set[2];
371
372 $_ = $rule;
373 /^(.*) \(.*$/;
374 $_ = $1;
375 /^forwarded (.*)$/;
376 $rule = $1;
377 $allDaysBytes->{$day}{$rule} += $bytes;
378 }
379}
380
381# initialize data variables for a new rule - if it is new
382sub init_filter_id {
383 my($s, $ifile) = @_;
384
385 if (!defined $allDaysBytes->{$s}) {
386 if ($displayMode =~ /^daily/) {
387 my $newDay = &makemydailytime($s);
388 $newDay =~ /^\d\d\d\d-(\d\d)-\d\d$/;
389
390 return 1 if ($1 > $curMonth && $displayMode ne "daily_multi");
391
392 $allDaysBytes->{$s}{'Day'} = $newDay;
393 }
394 else {
395 $allDaysBytes->{$s}{'Day'} = &makemymonthlytime($s);
396 }
397 $allDaysBytes->{$s}{${Traffic::blue_in}} = int(0);
398 $allDaysBytes->{$s}{${Traffic::green_in}} = int(0);
399 $allDaysBytes->{$s}{${Traffic::orange_in}} = int(0);
400 $allDaysBytes->{$s}{${Traffic::red_in}} = int(0);
401 $allDaysBytes->{$s}{${Traffic::blue_out}} = int(0);
402 $allDaysBytes->{$s}{${Traffic::green_out}} = int(0);
403 $allDaysBytes->{$s}{${Traffic::orange_out}} = int(0);
404 $allDaysBytes->{$s}{${Traffic::red_out}} = int(0);
405 }
406 return 0;
407}
408
409# read data record from filehandle $1
410# number of records is $2
411# Return value: reference to array a of length n;
412# n is the number of rules
413# each field in a is an array aa with 3 fields
414# the fields in arrays aa are: [0]=name of rule; [1]=byte count;
415# [2]=packet count
416# function does not use global variables
417sub read_data_record {
418 my($file, $number_of_records, $beforedata, $indata, $i, $irec);
419 my($pkts, $bytes, $rule);
420 my(@result);
421
422 $file=shift;
423 $number_of_records = shift;
424 $indata=0;
425 $beforedata=1;
426
427 for($irec = 0; $irec < $number_of_records; $irec++) {
428 $_ = <$file>;
429 chop;
430 /^\(\s*(.*)$/ or die "$me: bad line from fetchipac (expecting machine name): $_\n";
431 $machine_name = $1; # remember final machine name
432 while(<$file>) {
433 last if (/^\)$/); # terminating line ')'
434 /^(\d+)\s(\d+)\s\|(.*)\|$/
435 or die "$me: bad line from fetchipac (expecting rule item): $_\n";
436 $bytes = $1;
437 $pkts = $2;
438 $rule = $3;
439 if ($rule =~ /$rule_regex/) {
440 push(@result, [ $rule, $bytes, $pkts]);
441 }
442 }
443 }
444 # read another emtpy line (data format consistency)
445 $_ = <$file>;
446 die "$me: bad data from fetchipac (expected emtpy line): $_\n"
447 if ($_ !~ /^$/);
448 \@result;
449}
450
451# given a string in format YYYYMMDD[hh[mm[ss]]], make unix time
452# use time zone offset $tzoffset (input=wall clock time, output=UTC)
453sub makeunixtime {
454 my($y, $m, $d, $h, $i, $e);
455 $s = shift;
456
457 $h=0; $i=0; $e=0;
458 if ($s =~ /^(\d\d\d\d)(\d\d)(\d\d)/) {
459 ($y, $m, $d) = ($1, $2, $3);
460 if ($s =~ /^\d\d\d\d\d\d\d\d-?(\d\d)/) {
461 $h=$1;
462 if ($s =~ /^\d\d\d\d\d\d\d\d-?\d\d(\d\d)/) {
463 $i=$1;
464 if ($s =~ /^\d\d\d\d\d\d\d\d-?\d\d\d\d(\d\d)/) {
465 $e=$1;
466 }
467 }
468 }
469 }
470 else {
471 return 0;
472 }
473
474 $y-=1970;
475 $s = (($y)*365) + int(($y+2)/4) + $moff[$m-1] + $d-1;
476 $s-- if (($y+2)%4 == 0 && $m < 3);
477 $s*86400 + $h*3600 + $i*60 + $e + $tzoffset;
478}
479
480# return the given unix time in localtime in "mydaily" time format
481sub makemydailytime {
482 my($s)=shift;
483
484 my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
485 localtime($s);
486 return sprintf("%04d-%02d-%02d", 1900+$year, $mon+1, $mday);
487}
488
489# return the given unix time in localtime in "mymonthly" time format
490sub makemymonthlytime {
491 my($s)=shift;
492
493 my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
494 localtime($s);
495 return sprintf("%04d-%02d", 1900+$year, $mon+1);
496}
497
498# EOF