Если считать пакет 5 версии протокола NetFlow, то получим данные следующего вида:
00000000 00 05 00 1E │ 64 DB 5B 60 │ 51 23 3F B6 │ 38 E5 5D 5A │ 67 33 DF D6 │ 00 00 00 00
00000018 0A 4F E2 0A │ 0A 01 FF 01 │ C0 A8 22 02 │ 00 14 00 02 │ 00 00 00 02 │ 00 00 01 96
00000030 64 DA F4 3C │ 64 DB 11 88 │ E9 96 01 85 │ 00 10 11 00 │ 00 00 00 00 │ 14 10 00 08
00000048 0A 5C 03 04 │ 0A 4F E2 32 │ C0 A8 22 56 │ 00 02 00 14 │ 00 00 00 02 │ 00 00 00 D9
00000060 64 DA F4 40 │ 64 DB 1B 68 │ 00 35 C0 19 │ 00 10 11 24 │ 00 00 00 00 │ 10 14 00 00
00000078 0A 49 D0 08 │ 0A 4F E2 0A │ C0 A8 22 56 │ 00 02 00 14 │ 00 00 00 02 │ 00 00 02 8E
00000090 64 DA F4 C0 │ 64 DB 14 2C │ 00 8A 00 8A │ 00 10 11 24 │ 00 00 00 00 │ 10 14 00 00
000000A8 0A 4F A5 0A │ 0A 02 1A 8C │ C0 A8 22 02 │ 00 08 00 02 │ 00 00 00 03 │ 00 00 00 90
000000C0 64 DA F5 08 │ 64 DB 17 E4 │ 0E 8B 16 13 │ 00 02 06 00 │ 00 00 00 00 │ 14 10 00 08
******************************************************************************************
000003D8 0A 4F A1 46 │ 0A 48 38 1B │ C0 A8 22 02 │ 00 08 00 02 │ 00 00 00 0A │ 00 00 04 F2
000003F0 64 DB 3E 50 │ 64 DB 48 18 │ 11 9A 01 BB │ 00 1B 06 00 │
В соответствии со спецификацией протокола 5 версии, каждый такой пакет - таблица
Первые 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 - интервал между передачей данных. В нашем случае 0, значит данные будут направляться с сенсора сразу
Остальные данные — записи, которые разбиты по 48 байт. Ниже приведена таблица соответствия.
Bytes |
Contents |
Description |
0-3 |
srcaddr |
Source IP address |
4-7
|
dstaddr
|
Destination IP address |
8-11 |
nexthop |
IP address of next hop router |
12-13 |
input |
SNMP index of input interface |
14-15 |
output |
SNMP index of output interface |
16-19 |
dPkts |
Packets in the flow |
20-23 |
dOctets |
Total number of Layer 3 bytes in the packets of the flow |
24-27 |
First |
SysUptime at start of flow |
28-31 |
Last |
SysUptime at the time the last packet of the flow was received |
32-33 |
srcport |
TCP/UDP source port number or equivalent |
34-35 |
dstport |
TCP/UDP destination port number or equivalent |
36 |
pad1 |
Unused (zero) bytes |
37 |
tcp_flags |
Cumulative OR of TCP flags
|
38 |
prot |
IP protocol type (for example, TCP = 6; UDP = 17) |
39 |
tos |
IP type of service (ToS) |
40-41 |
src_as |
Autonomous system number of the source, either origin or peer |
42-43 |
dst_as |
Autonomous system number of the destination, either origin or peer |
44 |
src_mask |
Source address prefix mask bits |
45 |
dst_mask |
Destination address prefix mask bits |
46-47 |
pad2 |
Unused (zero) bytes |
Наличие такой жесткой структуры дает нам возможность применения шаблонов при распаковке и регулярных выражений. Модифицируем наш код:
#!/usr/bin/perl
use strict;
use IO::Socket::INET;
use DBI;
use POSIX;
use feature qw { switch };
$| = 1;
my ($socket,$received_data,$peer_address,$peer_port, $version);
$socket = new IO::Socket::INET (
LocalPort => '9999',
Proto => 'udp',
) or die "ERROR in Socket Creation : $!\n";
my ($dbname) = 'flow';
my ($dbhost) = '127.0.0.1';
my ($dbusr) = 'netflow';
my ($dbpass) = 'netflow';
my ($db) = DBI->connect("DBI:mysql:$dbname;host=$dbhost", "$dbusr", "$dbpass") || die "Could not connect to database: $DBI::errstr";
while(1) {
my ($recieved_data);
while(!$recieved_data) {
$socket->recv($recieved_data,4096);
$peer_address = unpack("N", $socket->peeraddr());
}
$version = unpack("n", substr($recieved_data,0,2));
given ( $version ) {
when ( 5 ) { fv5($recieved_data,$peer_address); }
}
}
$socket->close();
$db->disconnect();
sub fv5 {
my $sth = $db->prepare("SELECT COUNT(*) FROM devices WHERE `device_header`=$_[1]");
$sth->execute;
if ($sth->fetchrow == 0) {
$db->do("INSERT INTO devices (`device_header`) VALUES($_[1])");
}
my $sth = $db->prepare("SELECT `device_id` FROM `devices` WHERE `device_header`=$_[1]");
$sth->execute;
my $result = $sth->fetchrow_hashref;
my $dev_id = $result->{device_id};
my (@header) = unpack("nnN4NNNHHH2", substr($_[0],0,24));
my @records = ();
my ($i, $fmt, @substr);
my $data = "INSERT INTO `v5` VALUES\n";
for ($i=0; substr($_[0], 24+48*$i, 48); $i++) {
@substr = unpack("N3n2N4n2C4n2C2n", substr($_[0], 24+48*$i, 48));
$records[$i] = "('$dev_id','$header[3]','$header[2]','" . join("','", @substr) . "')";
}
$data .= join(",\n", @records);
$db->do("$data");
}
Для нормальной работоспособности нам понадобится инициализировать базу данных NetFlow на нашем сервере. Сделать это можно с помощью SQL скрипта:
-- Delete old base and user
DROP DATABASE `flow`;
DROP USER 'netflow'@'localhost';
-- Create new database and use it
CREATE DATABASE IF NOT EXISTS `flow` CHARACTER SET utf16;
USE `flow`;
-- Create user and grant privileges
CREATE USER 'netflow'@'localhost' IDENTIFIED BY PASSWORD
'*993AA45E0B64915AFBD1A5BE5713FD509A8E6C2C';
GRANT ALL PRIVILEGES ON `flow` . * TO 'netflow'@'localhost' WITH GRANT OPTION;
-- Create table for templates
CREATE TABLE IF NOT EXISTS `devices` (
`device_id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
`device_header` VARCHAR(100),
`device_description` VARCHAR(100),
`device_data` VARCHAR(100)
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS `v5` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
`device_id` INT UNSIGNED NOT NULL,
`datetime` INT UNSIGNED,
`sysuptime` INT UNSIGNED,
`srcaddr` INT UNSIGNED,
`dstaddr` INT UNSIGNED,
`nexthop` INT UNSIGNED,
`input` SMALLINT UNSIGNED,
`output` SMALLINT UNSIGNED,
`dpkts` INT UNSIGNED,
`doctets` INT UNSIGNED,
`first` INT UNSIGNED,
`last` INT UNSIGNED,
`srcport` SMALLINT UNSIGNED,
`dstport` SMALLINT UNSIGNED,
`pad1` TINYINT UNSIGNED,
`tcp_flags` TINYINT UNSIGNED,
`prot` TINYINT UNSIGNED,
`tos` TINYINT UNSIGNED,
`src_as` SMALLINT UNSIGNED,
`dst_as` SMALLINT UNSIGNED,
`src_mask` TINYINT UNSIGNED,
`dst_mask` TINYINT UNSIGNED,
`pad2` SMALLINT UNSIGNED
) ENGINE=InnoDB;
После инициализации БД и запуска скрипта мы получим некоторые данные в таблице v5 (если сенсор такженастроен корректно)
Что бы их просмотреть, можно воспользоваться примером SQL скрипта:
SELECT
FROM_UNIXTIME(`datetime`-CEIL((`sysuptime`-`first`)/1000)) AS first,
1000 - MOD(`sysuptime`-`first`, 1000) AS firstms,
FROM_UNIXTIME(`datetime`-CEIL((`sysuptime`-`last`)/1000)) AS last,
1000 - MOD(`sysuptime`-`last`, 1000) AS lastms,
INET_NTOA(`srcaddr`) as `srcaddr`,
INET_NTOA(`dstaddr`) as `dstaddr`,
INET_NTOA(`nexthop`) as `nexthop`,
`input`,
`output`,
`dpkts`,
`doctets`,
`srcport`,
`dstport`,
`tcp_flags`,
`prot`,
`tos`,
`src_as`,
`dst_as`,
`src_mask`,
`dst_mask`
FROM `v5` WHERE `device_id`=1
LIMIT 0, 30
Вывод на экран будет примерно следующим:
Примерной такой же вывод дает flow-print из пакета flow-tools. Различие заключается в механизме представления. Здесь вам необходимо обладать навыками в SQL что бы получать необходимые данные.
|