Что бы не мешать основному процессу, добавляю на сенсоре строчку для дублирования инфы на другой порт:
# conf t # ip flow-export destination xxx.xxx.xxx.xxx 9997
Циска ругнется, что мы дублируем инфу, но послушно выполнит.
Теперь надо как-то взять информацию: Пишем на перле (!PhP) скрипт:
#!/usr/bin/perl use IO::Socket::INET; $| = 1; my ($socket,$received_data); my ($peeraddress,$peerport); $socket = new IO::Socket::INET ( LocalPort => '9997', Proto => 'udp', ) or die "ERROR in Socket Creation : $!\n"; while(!$recieved_data) { $socket->recv($recieved_data,1464); $peer_address = $socket->peerhost(); $peer_port = $socket->peerport(); }
open (MYFILE, '>data.txt'); # print MYFILE "$peer_address:$peer_port\n"; # <- строчка просто что бы показать метод. print MYFILE $recieved_data; close (MYFILE);
Каждый такой пакет — таблица. Первые 24 байта (0-23) — это шапка:
00 05 - версия протокола 5 00 1E - в таблице будет 30 записей 64 DB 5B 60 - System uptime в милисекундах (переводить надо?) 51 23 3F B6 - Текущее время в секундах (с начала эпохи) 38 E5 5D 5A - Остаточная часть в НАНОСЕКУНДАХ!!! (с начала эпохи) 67 33 DF D6 - Номер последовательности. 00 - тип движка 00 - идентификатор движка 00 00 - sampling_interval - Если честно я так и не понял что это за фигня.
Остальные данные — записи, которые разбиты по 48 байт.
Здесь я наблюдаю некоторую избыточность: 1) Зачем под версию выделено 2 байта?! То есть планируется больше 255 версий? 2) Очень странно, что под остаточную часть выделяется 4 байта Зачем такая точность?!.. Мне бы хватило 2. 3) Тип движка и его идентификатор. Что то мне подсказывает, что это тоже не очень важные данные. 4) Sampling interval — еще 2 байта, заполненные нулями. Итого около 7 байт ненужной информации, которую врятли кому понядобятся. Учитывая тот факт, что пятая версия модифицироваться не будет, сомнений о бесполезности не остается. 7 байт тебе жалко что ли? — Умножаем эти 7 байт на миллионы пакетов, которые передаются и получаем ощутимое множество тактов оборудования. NetFlow и так очень прожорливый протокол. В самих записях есть все необходимые данные по потокам. Вероятно инженеры Cisco разрабатывали такой вид таблиц для какой то своей базы с особой структурой, в которой имеют значения, взятые из заголовков. Но в обычном быту системного администратора (вот только не надо ржать) это не очень нужно. С другой стороны если мы захотим, например следить за устройством, нам все равно придется выкачивать весь объем данных. Вероятно в этом вопросе решили подойти со всех сторон. Вспомним хотя бы тот факт, что маршрутизатор не принимает никаких запросов, а просто шлет накопленную информацию. Теперь давайте подумаем что мы можем сделать?.. Для начала декодируем пакет в удобоваримый вид:
#!/usr/bin/perl use IO::Socket::INET; use POSIX;
$| = 1;
my ($socket,$received_data); my ($peeraddress,$peerport);
$socket = new IO::Socket::INET ( LocalPort => '9997', Proto => 'udp', ) or die "ERROR in Socket Creation : $!\n";
open (MYFILE, '>data.txt'); print MYFILE "Source: $peer_address:$peer_port\n", "Protocol version: $header[0]\n", "Records count: $header[1]\n", "System uptime: $header[2] ms\n", "Timer: " . strftime ("%Y/%m/%d %H:%M:%S", localtime($header[3])) . "\n"; print MYFILE "\nsource_ip\t", "dst_ip\t\t", "next_hop\t", "if_in\t", "if_out\t", "Pkts\t", "Octets\t", "Time start\t\t", "Time end\t\t", "S_port\t", "D_port\t", "pad1\t", "Flag\t", "Prot\t", "TOS\t", "src_as\t", "dst_as\t", "s_mask\t", "d_mask\t", "pad2\n\n"; foreach $i (0..$header[1]) { if (substr($recieved_data, 24+48*$i, 48)) { @substr = unpack("A4 A4 A4 n n N N x8 n n C C C C n n C C n X24 N N", substr($recieved_data, 24+48*$i, 48)); if ($substr[18] && $substr[19]) { @t = localtime($header[3]+floor(($header[4]/1000000+$substr[18]-$header[2])/1000)); $substr[20] = fmod((floor($header[4]/1000000)+$header[2]-$substr[18]), 1000); $start = strftime "%Y-%m-%d.%H:%M:%S", @t; $start .= ".$substr[20]"; @t = localtime($header[3]+floor(($header[4]/1000000+$substr[19]-$header[2])/1000)); $substr[21] = fmod((floor($header[4]/1000000)+$header[2]-$substr[19]), 1000); $stop = strftime "%Y-%m-%d.%H:%M:%S", @t; $stop .= ".$substr[21]"; }; $fmt = "%vd\t%vd\t%vd\t\%d\t%d\t%d\t%d\t$start\t$stop\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n"; printf MYFILE ($fmt, @substr); } };
#print MYFILE $recieved_data; #debug info close (MYFILE);
$socket->close();
Если честно я очень лоханулся, не ознакомившись с работой сокетов Perl. Грабли меня ждали в строчке:
$socket->recv($recieved_data,1464);
У меня было подозрение, что маршрутизатор работает неправильно, говоря в заголовке о том, что передает 30 записей, а передавая на самом деле 20. Дак вот я просто скопировал из примера неправильное число байт, которое будет ожидать коллектор (1024). На самом деле мы должны ожидать 24+48*30=1464 байт. Только постоянные сомнения в себе помогли мне исправиться.
Вдогонку скрипт просмотра потока netflow в реальном внемени. Для него не нужно вообще ничего кроме установленного Perl. Возможно пригодится для диагностики.
#!/usr/bin/perl #Realtime viewer made by DreamHunter use IO::Socket::INET; use POSIX;
$| = 1;
my ($socket,$received_data); my ($peeraddress,$peerport);
$socket = new IO::Socket::INET ( LocalPort => '9997', Proto => 'udp', ) or die "ERROR in Socket Creation : $!\n";
while(1) { $socket->recv($recieved_data,1464); $peer_address = $socket->peerhost(); $peer_port = $socket->peerport(); @header = unpack("nnN4NNNHHH2", substr($recieved_data,0,24)); for ($i=0; substr($recieved_data, 24+48*$i, 48); $i++) { @substr = unpack("A4 A4 A4 n n N N x8 n n C C C C n n C C n X24 N N", substr($recieved_data, 24+48*$i, 48)); if ($substr[18] && $substr[19]) { @t = localtime($header[3]+floor(($header[4]/1000000+$substr[18]-$header[2])/1000)); $substr[20] = fmod((floor($header[4]/1000000)+$header[2]-$substr[18]), 1000); $start = strftime "%Y-%m-%d.%H:%M:%S", @t; $start .= ".$substr[20]"; @t = localtime($header[3]+floor(($header[4]/1000000+$substr[19]-$header[2])/1000)); $substr[21] = fmod((floor($header[4]/1000000)+$header[2]-$substr[19]), 1000); $stop = strftime "%Y-%m-%d.%H:%M:%S", @t; $stop .= ".$substr[21]"; $fmt = "%vd\t%vd\t%vd\t\%d\t%d\t%d\t%d\t$start\t$stop\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n"; printf ($fmt, @substr); } } }; $socket->close();
Послесловие: Лично для себя я считаю тему NetFlow версии 5 раскрытой. Предыдущие версии рассматривать нет смысла — там практически тоже самое. То есть работа для спинного мозга (тупо долбить код). Впереди ждет 9-я версия, которая призвана работать с протоколом IPv6.