Netflow-искания Часть 3.1 (изобретаем анализатор)

Блог им. dreamhunter
Очередным доказательством того что своя на работе только кружка послужило стирание виртуалки с NetFlow анализатором. Причины по сути довольно смешные. Не смешная только возможность потерять данные. В связи с этим решил создать новый топик, где поделюсь своими исследованиями и сохраню методы решения задачи для себя.

Преамбула: Есть такая классная штука, как сервер видеонаблюдения. Еще есть не менее классная штука как сервер ВКС. И они очень любят кушать канал связи. 10 мегабит для них — воронка, в которую они пытаются просунуть свой арбуз. Одним весенним утром наш Мега-Босс захотел воспользоваться конференц-связью дабы отыметь своих подчиненных пусть не физически, но хоть так. И что характерно — отымел, но совсем не тех кого ожидали, а наш отдел. Качество связи было отвратительным.
Естественно сам факт совершенного насилия над нами был лишь следствием. Захотелось узнать истинные причины этого. Одной из задач стояло составление очень сложного отчета по трафику в разрезах по сервисам. То есть грубо говоря необходимо было поставить на мониторинг и детально разобрать трафик для выяснения причин плохого качества ВКС.

Задача: Сформировать сложный, но читабельный для всех отчет по трафику по заданным периодам в разрезе по различным сервисам/источникам данных.

Исходные данные: Сырые данные, собранные Flow-Tools.

С чего начать?


Итак, для начала нам необходимо понимание следующих параметров.
1) Сырые данные из себя представляют строки, в которых содержится информация:
— время начала/окончания сессии
— адрес/порт/интерфейс источника/получателя
— объем данных/кол-во пакетов/кол-во сессий
2) Нам необходимо дискретизировать данные с нужными интервалами. То есть разложить данные по полочкам так, что бы они отражали полную картину происходящего.

Вот пример стандартного вывода программы flow-print:
# flow-cat -p /var/db/flows/r1/2013/2013-03/* | flow-print -f5

Start             End               Sif   SrcIPaddress    SrcP  DIf   DstIPaddress    DstP    P Fl Pkts       Octets

0317.00:04:57.787 0317.00:04:58.955 13    10.xx.xx.14    40672 10    10.xx.xx.226   31536 6   3  13         2982      
******************************************************************************************************************

Теперь давайте рассмотрим положение пакета в общем графике:
Время	Пакет 1	Пакет 2	Пакет 3
0:00:00	12441		
0:01:00	41241		12312
0:02:00	41241	35234	15125
0:03:00	41241	23423	
0:04:00	4123	23452	
0:05:00		14623	

В графическом виде это выглядит так:

В общем к этому виду нам и предстоит привести наш график. Только нам необходимо группировать одинаковые пакеты и суммировать их. Главной особенностью здесь является то, что временные интервалы разные. Мало того передача данных может начаться в любой момент в любой интервал времени. То есть нам необходимо вычислить сколько байт передалось в первом интервале, выяснить и вычислить сколько передалось в среднем в каждом последующем и потом посчитать сколько было в «хвосте». Хвост тоже скорей всего закончится где то в середине интервала.

От слов к делу


Подготавливаем фильтр для выгрузки и сохраняем с именем nflow.acl:
filter-primitive citrix
  type ip-port
  permit ica
  permit 1604
  permit 2512
  permit 2513
  permit 2598
  permit 2597

filter-primitive ipdpm
  type ip-address
  permit 10.xx.xx.178
  permit 10.xx.xx.130
  permit 10.xx.xx.36
  permit 10.xx.xx.39
  default deny

filter-primitive ipvks
  type ip-address
  permit 10.xx.xx.8
  permit 10.xx.xx.9
  permit 10.xx.xx.175
  permit 10.xx.xx.154
  default deny

filter-primitive voip
  type ip-address
  permit 10.xx.xx.3
  permit 10.xx.xx.60
  permit 10.xx.xx.61
  default deny

filter-primitive stime1
  type time
  permit ge 08:00:00
  default deny

filter-primitive etime1
  type time
  permit lt 21:00:00
  default deny

filter-primitive stime
  type time-date
  permit ge 03/17/2013 08:00:00
  default deny

filter-primitive etime
  type time-date
  permit lt 03/17/2013 12:00:00
  default deny

filter-primitive mail
  type ip-address
  permit 10.xx.xx.232
  permit 10.xx.xx.233
  permit 10.xx.xx.234
  default deny

filter-primitive summary
 type ifindex
 permit 2

filter-definition citrix_out
  match ip-source-port citrix
  match start-time stime1
  match end-time etime1

filter-definition citrix_in
  match ip-destination-port citrix
  match start-time stime1
  match end-time etime1

filter-definition ipdpm_out
  match ip-source-address ipdpm
  match start-time stime1
  match end-time etime1

filter-definition ipdpm_in
  match ip-destination-address ipdpm
  match start-time stime1
  match end-time etime1

filter-definition ipvks_out
  match ip-source-address ipvks
  match start-time stime1
  match end-time etime1

filter-definition ipvks_in
  match ip-destination-address ipvks
  match start-time stime1
  match end-time etime1

filter-definition voip_out
  match ip-source-address voip
  match start-time stime1
  match end-time etime1

filter-definition voip_in
  match ip-destination-address voip
  match start-time stime1
  match end-time etime1

filter-definition mail_out
  match ip-source-address mail
  match start-time stime1
  match end-time etime1

filter-definition mail_in
  match ip-destination-address mail
  match start-time stime1
  match end-time etime1

filter-definition timer
  match start-time stime1
  match end-time etime1

filter-definition summary_out
  match output-interface summary
  match start-time stime1
  match end-time etime1

filter-definition summary_in
  match input-interface summary
  match start-time stime1
  match end-time etime1

По понятным причинам IP адреса скрыты. Но вам же нужен только сам пример. Здесь их более чем достаточно.

Создаем Perl-скрипт.
#!/usr/local/bin/perl
# Загружаем библиотеки
use POSIX;
use Time::Local;

# Указываем данные по умолчанию
# 1) Укажем группы, по которым будем генерировать отчеты
my @filter = ("citrix_in", "citrix_out", "ipdpm_in", "ipdpm_out", "ipvks_in", "ipvks_out", "voip_in", "voip_out", "mail_in", "mail_out", "summary_in", "summary_out");

# 2) Укажем папку, где лежат данные
$src_dir="/var/db/flows/r1";
$repstart = "2013-03-25 08:00:00";
$repend = "2013-03-25 21:00:00";
@repstart = split(/[- :]/,$repstart);
@repend = split(/[- :]/,$repend);
$repstart = timelocal($repstart[5],$repstart[4],$repstart[3],$repstart[2],$repstart[1]-1,$repstart[0]);
$repend = timelocal($repend[5],$repend[4],$repend[3],$repend[2],$repend[1]-1,$repend[0]);
$src_dir .= strftime ("/%Y/%Y-%m", localtime($repstart));
$src_file="ft-v05.*";

# 3) Укажем место, куда складывать данные
$dst_dir="/usr/local/www/apache22/data/netflow/r1";
$dst_file = strftime ("%Y-%m-%d", localtime($repstart));

# 4) Зададим интервалы, вычислим их количество и заполним первые столбцы
$divider = 60; #seconds
$elements = int(($repend-$repstart)/$divider);
my @result = (0..$elements+1);
foreach ($i=0; $i<=$elements+1; $i++) {
	$result[$i] .= " " . strftime ("%Y/%m/%d %H:%M:%S", localtime($repstart + $divider * $i));
}

# Соберем файлы за период в один временный
system ("flow-cat -t ". strftime ("\"%m/%d/%Y %H:%M:%S\"", localtime($repstart)) ." -T ". strftime ("\"%m/%d/%Y %H:%M:%S\"", localtime($repend)) ." $src_dir/$src_file* > ./tmp.cat");

# Помашем ручкой пользователю
print strftime ("Начало периода    : %Y/%m/%d %H:%M:%S\n", localtime($repstart));
print strftime ("Окончание периода : %Y/%m/%d %H:%M:%S\n", localtime($repend));
print "Источник данных: $src_dir/$src_file\n";
print "Вывод данных   : $dst_dir/$dst_file\n";

# Начинаем перебирать фильтры поочереди
for ($i=0; $i<=$#filter; $i++) {
print "Обработка фильтра: $filter[$i]\n";
# Извлекаем данные по фильтру в массив
	$g = "cat ./tmp.cat | flow-nfilter -f nflow.acl -F$filter[$i] | flow-print -f5 -w | grep -v '^\$\\|[S]'";
	my @output = `$g`;
# Начинаем перерабатывать каждую строчку
	foreach (@output) {
# Разбиваем данные
		@str = split (' ', $_);
# Получаем дату/время начала/окончания сессии
		$datetime1 = timelocal(substr($str[0],11,2),substr($str[0],8,2),substr($str[0],5,2),substr($str[0],2,2),substr($str[0],0,2)-1,strftime "%Y", localtime);
		$datetime2 = timelocal(substr($str[1],11,2),substr($str[1],8,2),substr($str[1],5,2),substr($str[1],2,2),substr($str[1],0,2)-1,strftime "%Y", localtime);
# Выясняем к каким интервалам принадлежат данные
		$interval1 = ceil($datetime1/$divider)*$divider;
		$interval2 = floor($datetime2/$divider)*$divider;
# Если начало и окончание сессии находятся в одном интервале, то можем смело добавлять данные в результат
		if ($interval1 > $interval2 || $datetime1 == $datetime2) {
			@tmp = split(" ", $result[($interval1-$repstart)/$divider]);
			$tmp[$i+3] += $str[11];
			$result[($interval1-$repstart)/$divider] = join(" ", @tmp);
# Если пакет закончился в другом интервале, придется повозиться
		} else {
# Вычислим временной интервал самого пакета и вычислим скорость, с которой он был передан
			$interval = $datetime2 - $datetime1;
			$bps = $str[11]/$interval;
# Вычислим, в скольких интервалах он был передан
			$intervals = floor($interval/$divider);
# Выхватываем данные из результирующей таблицы
			@tmp = split(" ", $result[($interval1-$repstart)/$divider]);
# Прибавляем данные первого интервала в результирующую таблицу
			$tmp[$i+3] += int($bps*($interval1-$datetime1));
			$str[11] -= int($bps*($interval1-$datetime1));
			$result[($interval1-$repstart)/$divider] = join(" ", @tmp);
# Если данные передавались дольше двух интервалов, то записываем данные и туда
			for ($j=1; $j<=($interval2-$interval1)/$divider; $j++) {
				@tmp = split(" ", $result[($interval1-$repstart)/$divider+$j]);
				$tmp[$i+3] += ceil($bps*$divider);
				$str[11] -= ceil($bps*$divider);
				$result[($interval1-$repstart)/$divider+$j] = join(" ", @tmp);
			}
# Записываем оставшиеся данные
			@tmp = split(" ", $result[($interval2-$repstart)/$divider+1]);
			$tmp[$i+3] += $str[11];
			$result[($interval2-$repstart)/$divider+1] = join(" ", @tmp);
		}
	}
}
# Выгружаем данные
open FH, ">$dst_dir/$dst_file.r1.txt" or die "can't open '$dst_dir/$dst_file.r1.txt': $!";
	print FH "Num Date Time " . join (" ", @filter) . "\n";
foreach (@result) {
	@str = split (' ', $_);
	print FH "@str\n";
}
close FH; 

У меня на моем двух-ядерном виртуальном сервере с 4GB RAM скрипт вертелся около получаса. Занимаемые данные при обработке фильтров по summary за период 08:00-21:00 заняли около 3 GB RAM. Так что скрипт не для слабеньких лэптопов. Этот скрипт не идеален. Можно переделать его для более слабых машин. Для этого нужно что бы загонялись не все данные в анализатор, а по одному файлу сырых данных. Я сделал так потому что мне позволяли мощности и меньше было тактов считывания с HDD — медленного ресурса по сравнении с RAM.

Результат выполнения скрипта:
Num Date Time citrix_in citrix_out ipdpm_in ipdpm_out ipvks_in ipvks_out voip_in voip_out mail_in mail_out other_in other_out summary_in summary_out
0 20.03.2013 8:00:00 0 0 0 0 0 212 0 212 0 424 21861 14332 21861 15180
1 20.03.2013 8:01:00 661635 143087 14351 322944 13471 18621 1668 2684 893377 687360 14950145 22610000 16534647 23784696
2 20.03.2013 8:02:00 1033892 461407 39239 499104 13172 18778 60788 154560 930375 1858221 25032816 33081526 27110282 36073596
3 20.03.2013 8:03:00 1017138 549801 39376 411056 15161 19616 65031 183668 8717512 2175315 29475611 40723793 39329829 44063249
4 20.03.2013 8:04:00 1268099 953224 66157 389924 13727 18756 64843 183644 5684736 2492391 35813805 51992726 42911367 56030665
5 20.03.2013 8:05:00 1341831 577021 80652 817644 12464 17510 32847 162667 1166205 5237205 20944898 57449967 23578897 64262014
6 20.03.2013 8:06:00 1085728 495603 102499 2021038 14024 20144 1136 3000 1331851 9037599 40422819 61933222 42958057 73510606
7 20.03.2013 8:07:00 1150563 828136 102289 3061312 14881 19985 1136 2400 4558243 10824195 37094418 59640572 42921530 74376600


Вывод данных


Простейший способ отображения данных:
1) Cкопировать данные в MS-Word и преобразовать в таблицу
word

2) Скопировать таблицу в MS-Excel и пересчитать объем на скорость. Я это сделал на отдельном листе для удобства
excel

3)Теперь мы можем нарисовать график. Я сделал вот такой:
График

Мы еще и на Shell умеем немного.


Если у вас слабенький сервер, а некоторые данные получать хочется, вот вам пример облегченной версии для получения суммарных отчетов за сутки.
#!/bin/sh
SRC=/var/db/flows/r1/2013/2013-03/ft-v05.2013-03-24*
DST=/usr/local/www/apache22/data/netflow
unlink $DST/report.r1.txt
 for j in citrix_in citrix_out ipdpm_in ipdpm_out ipvks_in ipvks_out voip_in voip_out mail_in mail_out sharepoint_in sharepoint_out omnis_in omnis_out summary_in summary_out
  do
   OUT=`flow-cat $SRC | flow-nfilter -f nflow.acl -F$j | flow-stat -f0 | grep 'Total Octets' | cut -d : -f 2 | cut -c 2-`
   if [ "$OUT" ]
    then
     printf "$j $OUT\n" >> $DST/report.r1.txt
    else
      printf "$j 0\n" >> $DST/report.r1.txt
   fi
 done

Вывод этого скрипта немного отличается от вывода Perl скрипта. Данные будут выстроены в один столбик. Однако в MSWord можно просто преобразовать в таблицу с разделителем «знак абзаца».

На следующее утро мой сервер стерли и поставили Windows. Потому что Perl и FreeBSD это не есть истинная вера. А данные мы так через эксель и гоним. Но зато по-новому.

P.S. Rest In Peace my dear NetflowServer. Your dump will be always in my backup storage.

7 комментариев

avatar
Нормаль. График можно такой прикрутить docs.sencha.com/ext-js/4-2/extjs-build/examples/charts/Area.html
avatar
Есть такая классная штука, как сервер видеонаблюдения. Еще есть не менее классная штука как сервер ВКС. И они очень любят кушать канал связи. 10 мегабит для них — воронка, в которую они пытаются просунуть свой арбуз
а QOS настривали?
avatar
ооо, это большная тема. Меня с ней достали уже. =)
avatar
Подскажите. Время (first и last) — это что за время?? Когда данные поступили на коллектор или когда они были захвачены сенсором??
avatar
не понял о чем ты?
avatar
fist и last это я из flow-export взял. Из твоей таблички: start и end. Это Время когда сенсор сработал? Не могу найти точной информации по данному вопросу.
avatar
Кое как вник в твой вопрос. Старайся формулировать свои мысли.
По делу:
Fist/Last или Start/End — это начало и конец сессии. Сенсор следит за ними постоянно. Когда сессия заканчивается — данные передаются коллектору.
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.