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