FastNetMon

Wednesday, 5 November 2008

Биллинг, сам код

Все файлы можно скачать вот отсюда: http://slil.ru/26302780
А теперь все файлы доступны в svn репо на Гугле, прошу:
svn checkout http://mybill.googlecode.com/svn/trunk/ mybill-read-only

Немного потюненая таблица для ulogd:
DROP TABLE IF EXISTS `ulog`;
CREATE TABLE `ulog` (
id INT UNSIGNED AUTO_INCREMENT UNIQUE,

raw_mac VARCHAR(80),

oob_time_sec INT UNSIGNED,
oob_time_usec INT UNSIGNED,
oob_prefix VARCHAR(32),
oob_mark INT UNSIGNED,
oob_in VARCHAR(32),
oob_out VARCHAR(32),

ip_saddr INT UNSIGNED,
ip_daddr INT UNSIGNED,
ip_protocol TINYINT UNSIGNED,
ip_tos TINYINT UNSIGNED,
ip_ttl TINYINT UNSIGNED,
ip_totlen SMALLINT UNSIGNED,
ip_ihl TINYINT UNSIGNED,
ip_csum SMALLINT UNSIGNED,
ip_id SMALLINT UNSIGNED,
ip_fragoff SMALLINT UNSIGNED,

tcp_sport SMALLINT UNSIGNED,
tcp_dport SMALLINT UNSIGNED,
tcp_seq INT UNSIGNED,
tcp_ackseq INT UNSIGNED,
tcp_window SMALLINT UNSIGNED,
tcp_urg TINYINT,
tcp_urgp SMALLINT UNSIGNED,
tcp_ack TINYINT,
tcp_psh TINYINT,
tcp_rst TINYINT,
tcp_syn TINYINT,
tcp_fin TINYINT,

udp_sport SMALLINT UNSIGNED,
udp_dport SMALLINT UNSIGNED,
udp_len SMALLINT UNSIGNED,

icmp_type TINYINT UNSIGNED,
icmp_code TINYINT UNSIGNED,
icmp_echoid SMALLINT UNSIGNED,
icmp_echoseq SMALLINT UNSIGNED,
icmp_gateway INT UNSIGNED,
icmp_fragmtu SMALLINT UNSIGNED,

pwsniff_user VARCHAR(30),
pwsniff_pass VARCHAR(30),

ahesp_spi INT UNSIGNED,

KEY index_id (id),
INDEX (ip_saddr),
INDEX (ip_daddr),
INDEX (oob_time_sec)
);


Модуль работы с БД DB.pm:
#!/usr/bin/perl
#===============================================================================
#
# FILE: DB.pm
#
# USAGE: use DB;
#
# DESCRIPTION: Some functions for work with db.
#
# REQUIREMENTS: DBI.
# BUGS: ---
# NOTES: ---
# AUTHOR: Pavel Odintsov (nrg),
# COMPANY:
# VERSION: 1.0
# CREATED: Mon Dev 10 23:53:08 SAMT 2007
# REVISION: ---
#===============================================================================

package DB;

use strict;
use warnings;
use Exporter 'import';
use DBI;

our @EXPORT = qw(db_connect db_disconnect vquery query );
our $VERSION = '1.0';

our %DB_CONFIG = (); # hash for db settings
my $data_base_handle = ''; # variable for database handle
my $last_error = 'no error';


# connect to db, save db handle in private module variable $data_base_handle
sub db_connect {
my $db_connect_string = "DBI:mysql:database=$DB_CONFIG{'db_name'};" .
"host=$DB_CONFIG{'db_host'};" .
"port=$DB_CONFIG{'db_port'}"; # db connection string
$data_base_handle = DBI->connect($db_connect_string, $DB_CONFIG{'db_user'}, $DB_CONFIG{'db_passwd'}, { PrintError => 1, 'RaiseError' => 1 })
}


# disconnect from db
sub db_disconnect {
if ($data_base_handle) {
return ($data_base_handle->disconnect) ? 1 : 0;
}
return 0;
}


# execute query to db with data (preferably for SELECT)
sub query {
if ($data_base_handle) {
unless ($data_base_handle && $data_base_handle->FETCH('Active') && eval { $data_base_handle->ping }) { # check db connect
db_connect() or return;
}
map {
$_->execute(@_); # execute query
@{$_->fetchall_arrayref({})};
} $data_base_handle->prepare(shift)
} else {
return;
}
}


# execute query to db without data (proferably for INSERT, DELETE, UPDATE)
sub vquery {
if ($data_base_handle) {
unless ($data_base_handle && $data_base_handle->FETCH('Active') && eval { $data_base_handle->ping }) { # check db connect
db_connect() or return;
}
my $sql_request = shift;
$data_base_handle->do($sql_request, undef, @_) if $data_base_handle; # standard DBI do request
} else {
return;
}
}

1;



Основной файл:

#!/usr/bin/perl

use strict;
use warnings;
use lib qw/./;

use DB;
use Data::Dumper;
use CGI::Carp qw(fatalsToBrowser);
use CGI qw/:standard/;


%DB::DB_CONFIG = (
'db_name' =] 'traffic',
'db_port' =] q(),
'db_user' =] 'root',
'db_passwd' =] q(phahde4I),
'db_prefix' =] q()
);

my %dev_names = (
'00:e0:29:90:05:91:00:1c:f0:a9:78:9c:08:00 ' =] 'nrgs eeePC 900', # trailing space!!
);

# check entered date
sub check_date {
my $date = shift;

if ( $date =~ /\d{4}\-\d{1,2}\-\d{1,2}/ ) {
return 1;
}

return 0;
}

sub error_params {
print '[font color="red"]Диапазон дат указан неверно!!![/font]';
print '[/body][/html]';
exit;
}

print "Content-type: text/html\n\n";
print '[html][head][meta http-equiv="Content-Type" content="text/html; charset=utf8" /][/head][body]';

db_connect() or die "DB not avaliable\n";


my @devices = query("SELECT INET_NTOA(ip_daddr) AS dip, raw_mac FROM ulog GROUP BY ip_daddr ORDER BY ip_daddr");

my @month_dates = query("SELECT LAST_DAY(CURDATE()) AS last_day,
DATE_SUB( CURDATE(), INTERVAL DAY(CURDATE()) day) AS first_day;"); # -1 if last_day required

print [[DOC;
[a href ="/cgi-bin/bill.pl"]Статистика за сегодня[/a][br/]
[a href ="/cgi-bin/bill.pl?start_date=$month_dates[0]-]{first_day}&end_date=$month_dates[0]-]{last_day}"]Статистика за текущий месяц[/a][br/]
[form action="/cgi-bin/bill.pl" method="GET"]
Выдать статистику с [input type="text" name="start_date"] по [input type="text" name="end_date"]
[input type="submit" value="Сделать запрос"][br/]
Формат даты: YYYY-MM-DD (год-месяц-число)
Например, чтобы получить статистику за 1е января 2002года нужно ввести: 2002-01-01 2002-01-02
[/form]
[br][br]
DOC

if (@devices) {
print "В системе обнаружены следующие устройства:\n";

print '[ul]';
for (@devices) {
my $name = $dev_names{ $_-]{raw_mac} } || 'n/a';
print "[li]IP: $_-]{dip} Mac: $_-]{raw_mac} Имя: $name[/li]\n"
}
print '[/ul]';
}

my $start_date = param('start_date');
my $end_date = param('end_date');

my $posix_start_time = '';
my $posix_end_time = '';


if ( $start_date && $end_date ) {
if ( check_date($start_date) && check_date($end_date) ) {

my @period = query("SELECT UNIX_TIMESTAMP(?) AS start_period,
UNIX_TIMESTAMP(?) AS end_period", $start_date, $end_date );

if ( ($a = $period[0]-]{start_period} ) && ( $b = $period[0]-]{end_period} ) ) {
### print "[h3]Ы, параметры верны -- $a $b[/h3]";
( $posix_start_time, $posix_end_time ) = ( $a, $b );
} else {
error_params();
print 1;
}
} else {
error_params();
print 2;
}


} else {
# если диапазон не выбран, то текущий день
my @curr_day = query("SELECT UNIX_TIMESTAMP(CURDATE()) AS start_period,
UNIX_TIMESTAMP( date_add( CURDATE(), interval 1 day)) AS end_period,
CURDATE() AS cur_day,
DATE_ADD(CURDATE(), interval 1 day) AS next_day
;");


### print Dumper (\@curr_day);

( $posix_start_time, $posix_end_time ) = ( $curr_day[0]-]{start_period}, $curr_day[0]-]{end_period} );
( $start_date, $end_date ) = ($curr_day[0]-]{cur_day}, $curr_day[0]-]{next_day});

unless ($start_date && $end_date) {
print '[font color="red"]Хм, неожиданная ошибка[/font]';
print '[/body][/html]';
exit;
}
}


print "Статистика за указанный период ($start_date - $end_date):[br/][p][table]";
for (@devices) {
my @result = query("
SELECT SUM(ip_totlen) / 1024 / 1024 AS summ_traffic
FROM ulog
WHERE oob_time_sec ]= ? AND oob_time_sec [= ? AND ip_daddr = INET_ATON(?)",
$posix_start_time, $posix_end_time, $_-]{dip}
);
### print Dumper( [ '333', $posix_end_time, $posix_start_time] );
# ip_saddr
print "[tr][td]IP: $_-]{dip}[/td][td]Трафик: @{ [ $result[0]-]{summ_traffic} || 0 ] } mb[/td][/tr]\n";
}
print '[/table][/p]';

print "[/body][/html]";


Перед использованием заменить все [ на > и ] на >.

Вспомогательная инфа:

mysql -uroot -pphahde4I -Dtraffic -e 'source scheme.sql;'
http://192.168.155.51/cgi-bin/bill.pl?start_date=2002-1-1&end_date=2010-1-22
sudo tar -cjf nrgs_billing.tar.bz2 *



Собственно, сия программенция (web-интерфейс) умеет показывать через трафик, собранный демоном ulog.
Умеет показывать "за сегодня", "за текущий месяц", "за требуемый диапазон". Лицензия -- Artistic.
Пожелания, исправления, запросы фич -- приниманиются :) // хотя навряд ли кому такое нуна, но все же :)

2 comments :

  1. и как оно работает, быстро?

    ReplyDelete
  2. Помирает через месяц, когда база переваливает за 1 миллион строк :)

    Надо переписать, но времени пока нету.

    ReplyDelete

Note: only a member of this blog may post a comment.