]> git.ipfire.org Git - ipfire-2.x.git/blob - config/cfgroot/net-traffic-lib.pl
HinzugefĆ¼gt:
[ipfire-2.x.git] / config / cfgroot / net-traffic-lib.pl
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
35 package Traffic;
36
37 use 5.000;
38 use Getopt::Long;
39 use POSIX qw(strftime);
40 use Time::Local;
41 use Socket;
42 use 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 ##
65 my %allDays = ();
66 my $allDaysBytes;
67 my $tzoffset = 0;
68 my $displayMode = "daily";
69 my ($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
80 sub 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.
169 sub 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
308 sub 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
352 sub 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
382 sub 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
417 sub 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)
453 sub 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
481 sub 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
490 sub 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