]> git.ipfire.org Git - people/teissler/ipfire-2.x.git/blame - config/cfgroot/net-traffic-lib.pl
VPN Checksubnets: Now the remote subnets (OpenVPN/IPSec) are checked. If they are...
[people/teissler/ipfire-2.x.git] / config / cfgroot / net-traffic-lib.pl
CommitLineData
10a04d70
MT
1#!/usr/bin/perl
2#
d81292e0 3# $Id: net-traffic-lib.pl,v 1.10 2007/01/09 19:00:35 dotzball Exp $
10a04d70
MT
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;
d81292e0
CS
43#use warnings;
44#use strict;
10a04d70
MT
45
46$|=1; # line buffering
47
d81292e0 48my @moff = (0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 );
10a04d70
MT
49
50# =()<$datdelim="@<DATDELIM>@";>()=
d81292e0 51my $datdelim="#-#-#-#-#";
10a04d70 52# =()<$prefix="@<prefix>@";>()=
d81292e0 53my $prefix="/usr";
10a04d70 54# =()<$exec_prefix="@<exec_prefix>@";>()=
d81292e0 55my $exec_prefix="${prefix}";
10a04d70 56# =()<$INSTALLPATH="@<INSTALLPATH>@";>()=
d81292e0
CS
57my $INSTALLPATH="${exec_prefix}/sbin";
58my $datdir="/var/log/ip-acct";
10a04d70 59
d81292e0 60my $me=$0;
10a04d70 61$me =~ s|^.*/([^/]+)$|$1|;
d81292e0
CS
62my $now = time;
63my $fetchipac="$INSTALLPATH/fetchipac";
64my $rule_regex = ".*"; # match rules with this regex only
65my $machine_name;
66my $fetchipac_options;
67my ($newest_timestamp_before_starttime, $oldest_timestamp_after_endtime);
68my (%rule_firstfile, %rule_lastfile);
69my $count;
70my @timestamps;
71my $rulenumber;
72my ($starttime, $endtime);
10a04d70
MT
73
74## Net-Traffic variables ##
d81292e0 75my %allDays;
10a04d70
MT
76my $allDaysBytes;
77my $tzoffset = 0;
78my $displayMode = "daily";
79my ($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
90sub calcTraffic{
91 $allDaysBytes = shift;
92 $starttime = shift;
93 $endtime = shift;
94 $displayMode = shift;
d81292e0
CS
95
96 # init
97 %allDays = ();
10a04d70
MT
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
d81292e0
CS
107 if($displayMode ne "exactTimeframe")
108 {
109 $starttime = makeunixtime($starttime);
110 if($displayMode ne 'exactEnd') {
111 $endtime = makeunixtime($endtime);
112 }
113 }
10a04d70
MT
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);
d81292e0
CS
121#~ $mystarttime = &makemydailytime($starttime);
122#~ $myendtime = &makemydailytime($endtime);
123 %rule_firstfile = ( );
124 %rule_lastfile = ( );
125 @timestamps = ();
10a04d70
MT
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
d81292e0 163 push(@timestamps, $oldest_timestamp_after_endtime)
10a04d70 164 if ($oldest_timestamp_after_endtime);
d81292e0 165 unshift(@timestamps, $newest_timestamp_before_starttime)
10a04d70
MT
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
d81292e0 173 my @days_sorted = sort keys %allDays;
10a04d70
MT
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.
188sub 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 }
d81292e0 255 my $newMonth = $curYear;
10a04d70
MT
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.
d81292e0 264 # However, the timestamp gives us a clue on the
10a04d70
MT
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
d81292e0 278 # redundant). If we don't have a clue about the
10a04d70
MT
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);
d81292e0 287#~ $glb_data_before = $data;
10a04d70
MT
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) {
d81292e0
CS
301#~ $glb_data_after =
302#~ &split_data($data,$laststamp,$timestamp,$endtime);
303 &split_data($data,$laststamp,$timestamp,$endtime);
10a04d70
MT
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
328sub 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 = ( );
d81292e0
CS
357
358 foreach my $set (@$data) {
10a04d70
MT
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)
d81292e0 370# arguments:
10a04d70
MT
371# $1=index number of file; $2 = reference to array with data from file
372sub 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];
d81292e0 391
10a04d70
MT
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
402sub 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
d81292e0
CS
410 return 1 if ($1 > $curMonth && $displayMode ne "daily_multi");
411
10a04d70
MT
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
d81292e0 431# Return value: reference to array a of length n;
10a04d70
MT
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
437sub 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)
473sub makeunixtime {
474 my($y, $m, $d, $h, $i, $e);
d81292e0 475 my $s = shift;
10a04d70
MT
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
501sub 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
510sub 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