Понадобилось мне как-то реализовать подсчет трафика на шлюзе, с установленной на нем ОС FreeBSD 7.3. Для подсчета трафика я решил использовать модуль ng_netflow подсистемы netgraph. Статистика будет складываться в БД и через веб морду просматриваться. В качестве СУБД буду использовать PostgreSQL, но ничто не мешает использовать другую СУБД. В качестве коллектора и анализатора статистики я использовал пакет flow-tools.
Для начала нужно создать соответствующие узлы в netgraph. Для простоты развертывания на других машинах был написан следующий скрипт:
#!/bin/sh
# PROVIDE: ngnetflow
# REQUIRE: netif
# KEYWORD: nojail shutdown
. /etc/rc.subr
name="ngnetflow"
rcvar=`set_rcvar`
start_cmd="${name}_start"
stop_cmd=":"
load_rc_config $name
eval "${rcvar}=\${${rcvar}:-'NO'}"
ngnetflow_ifaces=${ngnetflow_ifaces:-""}
ngnetflow_export=${ngnetflow_export:-""}
ngnetflow_start()
{
if [ -z "${ngnetflow_ifaces}" ]; then
echo "ngnetflow_ifaces is not set"
return 1
fi
if [ -z "${ngnetflow_export}" ]; then
echo "ngnetflow_export is not set"
return 1
fi
count_if="0"
for iface in ${ngnetflow_ifaces}; do
ngctl mkpeer ${iface}: tee lower left
ngctl name ${iface}:lower ${iface}_tee
ngctl connect ${iface}: ${iface}_tee: upper right
ngctl mkpeer ${iface}_tee: one2many left2right many0
ngctl name ${iface}_tee:left2right ${iface}_tee_o2m
ngctl connect ${iface}_tee: ${iface}_tee_o2m: right2left many1
if [ "${count_if}" -eq "0" ]; then
ngctl mkpeer ${iface}_tee_o2m: netflow one iface0
ngctl name ${iface}_tee_o2m:one netflow
else
ngctl connect ${iface}_tee_o2m: netflow: one iface${count_if}
fi
count_if=`echo ${count_if} | awk '{ $0++; print $0; }'`
done
ngctl mkpeer netflow: ksocket export inet/dgram/udp
ngctl name netflow:export nf_ksocket
ngctl msg nf_ksocket: connect inet/${ngnetflow_export}
echo "Netflow system started..."
return 0
}
run_rc_command "$1"
Скрипт прост в использовании - его нужно скопировать в /usr/local/etc/rc.d, дать права на исполнение и сделать соответствующие записи в /etc/rc.conf.
ngnetflow_enable="YES" # разрешаем запуск скрипта при старте системы
ngnetflow_export="127.0.0.1:4444" # экспорт статистики на указанный здесь адрес
ngnetflow_ifaces="if0 if1" # интерфейсы, на которых нужно считать трафик
Далее нужно установить пакет flow-tools.
cd /usr/ports/net-mgmt/flow-tools
make install clean
После завершения установки добавляем следующие строки в /etc/rc.conf и запускаем коллектор:
flow_capture_enable="YES"
flow_capture_port="4444"
flow_capture_flags="-E 16M"
/usr/local/etc/rc.d/flow_capture start
Проверим запустился ли сервис:
sockstat -4 -l | grep flow
flowtools flow-captu 1008 1 udp4 *:4444 *:*
Если в выводе команды вы видите что-то подобное, то запуск прошел успешно.
Теперь запустим скрипт, который создаст соответствующие узлы в подсистеме netgraph.
/usr/local/etc/rc.d/ngnetflow start
Если скрипт не выдал никаких ошибок, значит запуск прошел успешно.
Для перегона статистики в БД я написал два скрипта. Первый запускается каждый день и запихивает в БД статистику за прошлый день. Второй запускается раз в месяц и создает новую таблицу в БД.
Первый скрипт (nf_daily.sh):
#!/bin/sh
# Скрипт экспортирует статистику за день в БД
REP_Y=`date -v-1d "+%Y"` # report year
REP_M=`date -v-1d "+%m"` # report month
REP_D=`date -v-1d "+%d"` # report day
WORKDIR="/root/netflow"
TMPDIR="/tmp"
FLOWS_DIR="/var/db/flows"
EXP_FORMAT="DPKTS,DOCTETS,SRCADDR,DSTADDR,NEXTHOP,INPUT,OUTPUT,SRCPORT,DSTPORT,PROT,TOS,TCP_FLAGS,SRC_MASK,DST_MASK"
DB_HOST="192.168.7.253"
DB_PORT="5432"
DB_USER="netflow"
DB_PASS="1234"
DB_NAME="netflow"
DB_TABLE="${REP_Y}-${REP_M}"
DB_COLUMNS="\
\"dpkts\",\
\"doctets\",\
\"srcaddr\",\
\"dstaddr\",\
\"nexthop\",\
\"input\",\
\"output\",\
\"srcport\",\
\"dstport\",\
\"proto\",\
\"tos\",\
\"tcp_flags\""
EXP_FILE="${TMPDIR}/netflow.csv"
SQL_FILE="${TMPDIR}/netflow.sql"
LOG_FILE="${WORKDIR}/nf_daily.log"
flow-cat "${FLOWS_DIR}/${REP_Y}/${REP_Y}-${REP_M}/${REP_Y}-${REP_M}-${REP_D}" | \
flow-export -f2 -m ${EXP_FORMAT} 2> /dev/null | \
egrep -v "^#" > ${EXP_FILE}
if [ ! -f ${EXP_FILE} ]; then
echo "Export file not found"
exit 1
fi
if [ -f ${SQL_FILE} ]; then
rm -f ${SQL_FILE}
fi
for line in `cat ${EXP_FILE}`; do
data=`echo ${line} | awk '{ split($0, str, ",");
printf("%u, %u, $$%s$$, $$%s$$, $$%s$$, %d, %d, %d, %d, %d, %d, %d",
str[1], str[2], str[3], str[4],
str[5], str[6], str[7], str[8],
str[9], str[10], str[11], str[12]);
}'`
echo "INSERT INTO \"${DB_TABLE}\"(${DB_COLUMNS}) VALUES (${data});" >> $SQL_FILE
done
psql -q -d ${DB_NAME} -h ${DB_HOST} -p ${DB_PORT} -U ${DB_USER} -f $SQL_FILE
rm -f ${EXP_FILE}
rm -f ${SQL_FILE}
exit 0
Второй скрипт (nf_month.sh):
#!/bin/sh
DB_HOST="192.168.7.253"
DB_PORT="5432"
DB_USER="netflow"
DB_PASS="1234"
DB_NAME="netflow"
DB_TABLE=`date "+%Y-%m"`
SQL_QUERY="CREATE TABLE \"${DB_TABLE}\" (\
\"id\" bigserial NOT NULL UNIQUE PRIMARY KEY,\
\"dpkts\" bigint,\
\"doctets\" bigint,\
\"srcaddr\" cidr,\
\"dstaddr\" cidr,\
\"nexthop\" cidr,\
\"input\" smallint,\
\"output\" smallint,\
\"srcport\" int,\
\"dstport\" int,\
\"proto\" smallint,\
\"tos\" smallint,\
\"tcp_flags\" smallint\
);"
psql -q -d ${DB_NAME} -h ${DB_HOST} -p ${DB_PORT} -U ${DB_USER} -c "${SQL_QUERY}" > /dev/null 2>&1
exit 0
Поправьте параметры подключения к БД на свои и не забудте дать права на запуск скриптам. Теперь добавим их в крон:
0 1 1 * * /root/netflow/nf_month.sh
30 0 * * * /root/netflow/nf_daily.sh
Осталось только создать таблицу за текущий месяц, чтобы скрипту nf_daily.sh было куда складывать статистику, для этого можно просто запустить скрипт nf_month.sh.
Вот еще веб морда, написанная на перле, для просмотра собранной статистики.
#!/usr/bin/perl
use strict;
use warnings;
my $DB_HOST = 'localhost';
my $DB_PORT = '5432';
my $DB_NAME = 'netflow';
my $DB_USER = 'netflow';
my $DB_PASS = '1234';
my $stat_month = '';
my $query_menu = '
SELECT
"tablename"
FROM "pg_tables"
WHERE "tableowner" = $$#owner#$$;';
my $query_content = '
SELECT
SUM("dpkts") AS "packets",
(SUM("doctets")/1048576)::bigint AS "size",
host("dstaddr") FROM "#tbl_name#"
WHERE "dstaddr" << $$192.168.7.0/24$$
GROUP BY "dstaddr"
ORDER BY "dstaddr";';
if ($ENV{'REQUEST_METHOD'} && ($ENV{'REQUEST_METHOD'} eq 'GET')){
my $param = $ENV{'QUERY_STRING'};
foreach (split(/&/, $param)){
my @str = split(/=/);
if ($str[0] eq 'month'){
$stat_month = $str[1];
$stat_month =~ s/'|"|\$|\(|\)//;
last;
}
}
}
print("Content-type: text/html\n\n");
$query_menu =~ s/#owner#/$DB_USER/;
system("psql -h $DB_HOST -p $DB_PORT -d $DB_NAME -U $DB_USER -t -c '$query_menu' -o /tmp/db_menu.txt");
if (length($stat_month) > 0){
$query_content =~ s/#tbl_name#/$stat_month/;
system("psql -h $DB_HOST -p $DB_PORT -d $DB_NAME -U $DB_USER -t -c '$query_content' -o /tmp/db_table.txt");
}
print('<html>',
'<head>',
'<title>Statistics</title>',
'<style type="text/css">',
'table { border-collapse: collapse; }',
'.header { background-color: lightblue; }',
'.content { background-color: orange; }',
'</style>',
'</head>',
'<body>'
);
sub get_menu(){
my $text = '';
if (!open(FIN, "< /tmp/db_menu.txt")){
return '<h3>Empty!!!</h3>'
}
$text .= join('', '<table align="center" border="1" width="100%">',
'<tr><td align="center" class="header">Menu</td></tr>'
);
while (<FIN>){
chomp;
next if length == 0;
s/\s|\t//g;
$text .= join('', '<tr><td align="center" class="content"><a href="?month=',
$_,
'">',
$_,
'</a></td></tr>'
);
}
$text .= '</table>';
close(FIN);
return $text;
}
sub get_content(){
my $text = '';
if (!open(FIN, "< /tmp/db_table.txt")){
return '<h3>Empty!!!</h3>';
}
$text .= join('', '<table border="1">',
'<tr><td class="header">packets</td>',
'<td class="header">megabytes</td>',
'<td class="header">IP</td></tr>'
);
while (<FIN>){
chomp;
next if length == 0;
$text .= '<tr>';
foreach (split(/\|/)){
chomp;
s/\s|\t//g;
$text .= join('', '<td class="content">', $_, '</td>');
}
$text .= '</tr>';
}
$text .= '</table>';
close(FIN);
return $text;
}
print('<table width="100%">',
'<tr><td align="center" valign="top" width="15%">',
get_menu(),
'</td>'
);
print('<td align="left" valign="top" width="85%">',
get_content(),
'</td></tr></table>',
'</body></html>'
);
system("rm -f /tmp/db_menu.txt /tmp/db_table.txt");
Ну на этом все, можно пользоваться.
Добавить комментарий