Как стать автором
Обновить

Разбор пакетов NetFlow v.9 на C#

Время на прочтение7 мин
Количество просмотров20K
NetFlow это сетевой протокол, созданный компанией Cisco Systems для учёта сетевого трафика. Наиболее распространёнными версиями данного протокола являются 5 и 9. Девятая версия более гибкая так как используются шаблоны, согласно которым присылаются данные. В пятой версии данные присылаются согласно спецификации.
image

Система сбора информации о трафике по протоколу NetFlow состоит из следующих компонентов:
  • Сенсор. Устройство (маршрутизатор, L3-коммутатор) собирающее статистику по проходящему через него трафику;
  • Коллектор. Занимается сбором данных от сенсора и помещает их в хранилище;
  • Анализатор. Анализирует данные, которые собрал коллектор и формирует отчёты.


О разработки части функций анализатора на C#, а точнее разбор пакетов NetFlow я и расскажу

В качестве сенсора был использован MikroTik маршрутизатор.

Включаем на нем NetFlow для ether1 интерфейса:
/ip traffic-flow set enabled=yes interfaces=ether1

И добавляем коллектор (как правило, коллектор слушает порт 2055, 9555 или 9995):
/ip traffic-flow target add disabled=no version=9 address=192.168.0.100:9995

Или тоже самое но через WinBox:
image

Теперь на компьютер с IP адресом 192.168.0.100 на 9995 порт по UDP (или SCTP) будут приходить пакеты NetFlow 9 версии. Пакеты приходят и есть с чем работать.

Разбор приходящих пакетов



Изучив спецификацию протокола узнаем, что каждый NetFlow пакет (N байт) состоит из:
  1. Packet Header (20 байт) — заголовок пакета, в единственном экземпляре с полями:
    • Version Number (UInt16 — 2 байта) — номер версии NetFlow, у нас всегда 9;
    • Count (UInt16 — 2 байта) — общее количество записей. Далее по тексту статей с этим полем было приключение;
    • sysUpTime (UInt32 — 4 байта) — время в миллисекундах со старта устройства — UpTime;
    • UNIX Secs (UInt32 — 4 байта) — время в секундах с 0000 UTC 1970, при котором отправили пакет;
    • Sequence Number (UInt32 — 4 байта) — счетчик переданных пакетов, он постоянно увеличивается от пакета к пакету, тем самым можно проверить потерялись ли пакеты между ними;
    • Source ID (UInt32 — 4 байта) — номер потока данных, деле в том, что со стороны сенсора могут идти несколько потоков данных.

  2. FlowSet (N-20 байт) — шаблоны, данные… FlowSet`ов может быть несколько или один. В каждом FlowSet`е есть два неизменных от типа передаваемых данных (шаблон, данные) поля:
    • FlowSet ID (UInt16 2 байта) — для шаблона это всегда 0, для опционального шаблона 1, для данных он равен Template ID и следовательно больше 255 (от 256 до 65535);
    • Length (UInt16 2 байта) — размер всего FlowSet вместе с полями FlowSet ID и Length;
    • Другие поля в зависимости от типа передаваемых данных.



Смотрим FlowSet ID содержащий шаблон, начинается с полей FlowSet ID, потом Length, далее:
  • Template ID (UInt16 2 байта) — уникальный ID для каждого шаблона по которому передаются данные. Число от 256 до 65535;
  • Field Count (UInt16 2 байта) — количество полей в шаблоне. Далее идут поочередно тип поля (Field Type) и размер (Field Length);
  • Field Type (UInt16 2 байта) — число определяющее тип поля. Все типы есть в спецификации протокола;
  • Field Length — длинна поля в байтах.


Смотрим FlowSet ID содержащий данные, начинается с полей FlowSet ID, потом Length, далее:
  • Данных… данных которые соответствуют полям и их размерам;
  • Padding — нули заполняющие до границы в 4 байта.


Есть еще так называемый опциональный шаблон и данные по нему. Я их рассматривать не буду, они мне не встречались, по этой причине в реализации библиотеки отсутствуют, но все можно дописать.

Составил UML диаграмму классов (с помощью NClass):
image
или в pdf
И написал библиотеку для разбора приходящих пакетов.
Основной класс с которого все начинается, это Packet. Его единственный конструктор принимает входящий пакет NetFlow в байтах и объект класса Templates, представляющий из себя список текущих Template (шаблонов).

Далее в конструкторе класса Packet вызывается функция Parse, которая принимает объект класса Templates.
В этой функции идет разбивка пакета на заголовок — 20 байт и дальнейшая работа с ним через класс Header; на FlowSet`ы и передача каждого FlowSet`а на обработка соответствующему классу FlowSet.

Ввиду того, что FlowSet`ов может быть несколько, приходится вторую часть пакета (без 20 байт заголовка), анализировать и разбивать на разные FlowSet`ы. Примечательно что в MikroTikFlowSet`ы в единственном экземпляре в пакете, а вот пользуясь Netflow Simulator in C# удалось поработать с пакетами с несколькими FlowSet`ами в пакете. Кроме того благодаря ему был найден забавный баг в реализации NetFlow v9 на MikroTik`е, о чем подробнее тут.

Netflow Simulator in C#:
image

Вот участок кода разбивающий часть пакета на FlowSet`ы:
this._flowset = new List<FlowSet>();
Int32 length = _bytes.Length - 20;
Byte[] flowset = new Byte[length];
Array.Copy(_bytes, 20, flowset, 0, length);
byte[] reverse = flowset.Reverse().ToArray();
int templengh = 0;
while ((templengh + 2) < flowset.Length)
{
	UInt16 lengths = BitConverter.ToUInt16(reverse, flowset.Length - sizeof(Int16) - (templengh+2));
	Byte[] bflowsets = new Byte[lengths];
	Array.Copy(flowset, templengh, bflowsets, 0, lengths);
	FlowSet flowsets = new FlowSet(bflowsets, templates);
	this._flowset.Add(flowsets);
	templengh += lengths;
}


В классе Header идет разбор заголовка пакета на его поля. Прежде чем это сделать, выполняется реверсия заголовка:
this._bytes.Reverse().ToArray();


Далее конвертируем биты в тот тип поля, которым он является, например поле version:
this._version = BitConverter.ToUInt16(reverse, this._bytes.Length - sizeof(Int16) - 0);


Да в Header поля sysUpTime имеет тип TimeSpan, приводим к этому типу:
get
{
	return new TimeSpan((long)this._uptime * 10000);
}


и поле UNIX Secs имеет тип DateTime:
get
{
	return new DateTime(1970, 1, 1).AddSeconds(this._secs);
}


Перейдем к обработке FlowSet`ов. После получения полей FlowSet ID и Length идет разбор остальных полей в зависимости от FlowSet ID. Если он равен 0 или 1 то это шаблон, а если это число от 256 до 65535 то это данные.

Если это шаблон то передаем его обработку классу Template после чего проверяем наше хранилище шаблонов (объект класса Templates) на наличие шаблона с таким же ID и заменяем его, иначе просто добавляем шаблон.

Если это данные то проверяем есть ли в хранилище (объект класса Templates) такой шаблон (FlowSet ID == Template ID) и если есть то копируем этот шаблон функцией DeepClone и заполняем его поля — Field, иначе ничего не делаем, ведь без шаблона это просто набор байтов.

функция DeepClone:
public static object DeepClone(object obj)
{
    object objResult = null;
    using (MemoryStream ms = new MemoryStream())
    {
        BinaryFormatter bf = new BinaryFormatter();
        bf.Serialize(ms, obj);

        ms.Position = 0;
        objResult = bf.Deserialize(ms);
    }
    return objResult;
}


Field — это поле, оно имеет следующие параметры:
  • Type — тип;
  • Length — размер;
  • Value — значение.


Причем Field в Template в хранилище находятся без параметров Value т.е. Value пусты, а вот при обработке пакетов Template в FlowSet в объекте Packet уже содержит поля Value.

Кроме всего этого есть еще перечисление FieldType — перечисление в котором имени типа соответствует номер данного типа. (параметр Type в Field)

Для работы данной библиотеки был написан пример:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.NetFlow;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace Consoles
{
    class Program
    {
        static void Main(string[] args)
        {
            Templates _templates = new Templates();

            Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            IPEndPoint iep = new IPEndPoint(IPAddress.Any, 9995);
            sock.Bind(iep);
            EndPoint ep = (EndPoint)iep;

            byte[] data = new byte[2048];

            while (true)
            {
                int recv = sock.ReceiveFrom(data, ref ep);
                Console.ReadKey();
                Console.Clear();
                byte[] bytes = new byte[recv];

                for (int i = 0; i < recv; i++)
                    bytes[i] = data[i];

                Packet packet = new Packet(bytes, _templates);
                
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.WriteLine(packet.ToString());
            }
            sock.Close();

            Console.ReadKey();
        }
    }
}


Создаем сокет и слушаем по UDP 9995 порт на нашем ПК. _templates — это наше хранилище шаблонов. Каждый приходящий пакет мы скармливаем объекту packet класса Packet передавая еще и наше хранилище шаблонов. А далее выполняем packet.ToString() Это перегруженная функция выводит нам содержание пакета и нужна только для проверки, что у нас все получается.

На этом с библиотекой все, теперь ее можно использовать для дальнейшего написания Анализатора трафика по NetFlow протоколу.

Пример с MikroTik:

Получили пакет без наличия шаблона в хранилище:
image

Получили шаблон от сенсора:
image

Получили данные, для которых есть шаблон в хранилище:
image

Ошибка реализации NetFlow v9 в MikroTik


В процессе разбора данной темы бал найден ошибка в реализации NetFlow v9 в MikroTik`е. Суть ошибки:
Поле Count в заголовке пакета (Packet Header) несет в себе:
Count
The total number of records in the Export Packet, which is the
sum of Options FlowSet records, Template FlowSet records, and
Data FlowSet records.

т.е. содержит все записи, во всех FlowSet`ах, а в MikroTik`е данное поле всегда равно 1 (см. скрины выше), даже если передается несколько шаблонов или данных. т.е. по логике MikroTik`а поле Count = количеству FlowSet`ов (о чем они мне и написали в письме и видно по скринам), а должно быть равно общему количеству всех шаблонов и данных, как звучит в спецификации. По этой причине использовать в разборе пакетов поле Count чревато.

Вот же пример от Netflow Simulator in C# (Хотелось бы получить данные и от Cisco, но у меня нет такой возможности, может кто из читателей проверит это):

Получили пакет без наличия шаблона в хранилище (обратите внимание на Count):
image

Получили шаблон от сенсора (тут одновременно два FlowSet`а пришло, что в MikroTik`е не бывает. Обратите внимание на Count он равен 7 = 1 шаблон и 6 записей с данными. По логике MikroTikCount должен был бы равен 2 = 2 FlowSet`а):
image

Получили данные, для которых есть шаблон в хранилище (обратите внимание на Count):
image

Ну и еще раз пакет в Wireshark Помечено поле Count:
image

Еще раз: буду очень благодарен всем кто пришлет скрин с Wireshark`ом от Cisco. Вставлю его сюда.

Исходный код доступен здесь.

При создании руководствовался:
Материал из Википедии: Netflow
Caligare: WHAT IS NETFLOW?
Спецификация протокола версии 9

Сегодня (19:05 30/07/2013)

MikroTik ответил:

MikroTik support [Dzintars] support@mikrotik.com
Hello,

Thank you for reporting a problem (the author of the article correctly pointed out
that count value in netflow packet header not always is set correctly). The
problem will be fixed in the next version of RouterOS.

Regards,
Dzintars


UPD: В версии RouterOS 6.2 данный баг исправлен.
Сегодня (13:22 13/09/2013)

MikroTik написал:

MikroTik support [Dzintars] support@mikrotik.com
Hello,

The NetFlow V9 count field problem was fixed in version 6.2

Regards,
Dzintars
Теги:
Хабы:
+4
Комментарии0

Публикации

Истории

Работа

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн