Подсчет трафика с помощью ng_netflow

Понадобилось мне как-то реализовать подсчет трафика на шлюзе, с установленной на нем ОС 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");

Ну на этом все, можно пользоваться.

Прикрепленные файлы

Добавить комментарий

CAPTCHA
Этот вопрос задается для того, чтобы выяснить, являетесь ли Вы человеком или представляете из себя автоматическую спам-рассылку.
Яндекс.Метрика