NetFlow v5 Python (часть 1)
NetFlow является замечательным инструментом для контроля трафика в сети. Для него существует масса коммерческих и бесплатных продуктов, но иногда системным администраторам хочется чего-то странного. Например, мне захотелось написать свой собственный коллектор и анализатор. Будто бы я смогу смогу сделать лёгкий, переносимый и оптимальный вариант который меня полностью удовлетворит. Естественно, что мне захотелось его написать на Python с использованием стандартных библиотек. Но поискав на просторах сети информацию по этому поводу я нашел, что масса примеров и попыток реализаций уходит корнями в пример господина Брайна Рэка. При этом его никто не упоминает, но регулярно выдают его код за свой. В большинстве случаев добавляется вывод в файл, вместо терминала. Опять же я тоже продублирую код из блога этого замечательного человека. Если вы знаете автора более начального уровня, то пишите в комментарии.
Сегодня только продублирую этот код и покажу результат его вывода и опишу своё видение идеального коллектора для NetFlow.
import socket, struct from socket import inet_ntoa SIZE_OF_HEADER = 24 SIZE_OF_RECORD = 48 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind(('0.0.0.0', 9996)) while True: buf, addr = sock.recvfrom(1500) (version, count) = struct.unpack('!HH',buf[0:4]) if version != 5: print ("Not NetFlow v5!") continue # It's pretty unlikely you'll ever see more then 1000 records in a 1500 byte UDP packet if count <= 0 or count >= 1000: print ("Invalid count %s" % count) continue uptime = socket.ntohl(struct.unpack('I',buf[4:8])[0]) epochseconds = socket.ntohl(struct.unpack('I',buf[8:12])[0]) for i in range(0, count): try: base = SIZE_OF_HEADER+(i*SIZE_OF_RECORD) data = struct.unpack('!IIIIHH',buf[base+16:base+36]) nfdata = {} nfdata['saddr'] = inet_ntoa(buf[base+0:base+4]) nfdata['daddr'] = inet_ntoa(buf[base+4:base+8]) nfdata['pcount'] = data[0] nfdata['bcount'] = data[1] nfdata['stime'] = data[2] nfdata['etime'] = data[3] nfdata['sport'] = data[4] nfdata['dport'] = data[5] nfdata['protocol'] = ord(buf[base+38]) except: continue # Do something with the netflow record.. print ("%s:%s -> %s:%s" % (nfdata['saddr'],nfdata['sport'],nfdata['daddr'],nfdata['dport']))
Этот код выводит на экран информацию о передаче пакетов с какого IP:PORT на какой IP:PORT передаются пакеты. Очень хорошая демонстрация получается на небольших каналах, таких как домашний роутер. Результат вывода приблизительно вот такой.
Пакет NetFlow состоит из двух частей, заголовка и группы записей. В заголовке хранится следующая информация:
Bytes | Contents | Описание |
---|---|---|
0-1 | version | Версия NetFlow протокола. Может быть 1, 5 или 9. |
2-3 | count | Количество пакетов с данными. Не может быть больше 30 пакетов. |
4-7 | sys_uptime | Время в миллисекундах с момента запуска сетевого оборудования. Можно использовать для контроля за перезапуском оборудования. Можно использовать как временной идентификатор. |
8-11 | unix_secs | UNIX-время (в секундах) на момент начала передачи пакетов. Количество секунд прошедшее с 0000 UTC 1970. Можно использовать как временной идентификатор. |
12-15 | unix_nsecs | UNIX-время (в наносекундах) на момент начала передачи пакетов. Количество наносекунд прошедшее с 0000 UTC 1970. Можно использовать как временной идентификатор. |
16-19 | flow_sequence | Номер записи. Позволяет контролировать потерю пакетов. |
20 | engine_type | Тип движка. |
21 | engine_id | Идентификатор движка. |
22-23 | sampling_interval | Количество выборок. Первые два бита содержат режим выборок. Остальное является интервалом выборок. Этим полем сенсор оповещает о том, что отправляется 1:N пакетов. Например, при высокой нагрузке на канал сенсор может отправлять один из 10000 пакетов. |
Пакеты с данными о трафике выглядят вот так:
Bytes | Contents | Описание |
---|---|---|
0-3 | srcaddr | IP-адрес источника трафика. |
4-7 | dstaddr | IP-адрес получателя трафика. |
8-11 | nexthop | IP-адрес следующего роутера. |
12-13 | input | Индекс входящего интерфейса. Они заново нумеруются при каждом запуске устройства, номера можно запросить через SNMP. |
14-15 | output | Индекс исходящего интерфейса. Они заново нумеруются при каждом запуске устройства, номера можно запросить через SNMP. |
16-19 | dPkts | Количество переданых пакетов. |
20-23 | dOctets | Количество переданых байтов. |
24-27 | first | SysUptime время генерации первого пакета. Вычислить абсолютное время генерации пакета можно используя информацию из заголовка. |
28-31 | last | SysUptime время генерации последнего пакета. Вычислить абсолютное время генерации пакета можно используя информацию из заголовка. |
32-33 | srcport | Порт источника. |
34-35 | dstport | Порт получателя. |
36 | pad1 | Не используется |
37 | tcp_flags | Все TCP-флаги наблюдаемые во время замера. |
38 | prot | IP протокол (1=ICMP, 6=TCP, 17=UDP, 47=GRE и т.д.). |
39 | tos | Тип сервиса ToS. Тип обслуживания позволяет приоритизировать IP-трафик на сетевых маршрутизаторах. |
40-41 | src_as | Номер автономной системы источника. |
42-43 | dst_as | Номер автономной системы приёмника. |
44 | src_mask | Префикс сети источника |
45 | dst_mask | Префикс сети приёмника. |
46-47 | pad2 | Не используется. |
Данных очень много, что позволит получить максимально точные данные от сенсора.
Какие подводные камни тут могут скрываться? Например, номера интерфейсов (физических портов) перегенерируются при каждой перезагрузке сетевого оборудования, по этому необходимо зафиксировать эти номера. Либо при каждой перезагрузке создавать новую табличку с номерами интерфейсов. Запрашивать имена интерфейсов можно используя SNMP. Но эту часть я вынесу из области коллектора, коллектор должен собирать информацию и возможно отправлять в БД.
Где хранить информацию о собранных данных? Первое, что приходит в голову это кидать в БД. Но тут кроется небольшой подвох, мне не нравится иметь несколько точек отказа. Я бы хотел хранить данные на диске, но хранить их на диске крайне неудобно. Так, что я пока думаю. Вероятно сделаю некий гибрид, буду складировать из коллектора информацию в файлы и потом с определённой периодичностью проходить по ним, нормализовать и записывать в БД. Некий UNIX-подход, иметь массу узкозаточенных инструментов.
Номер протокола. Я остановился на реализации пятой версии протокола. Но разница между первым и пятым минимальная. В пятой версии добавлена следующая информация: Sampling interval, общий счетчик переданых NetFlow пакетов, номера входящей/исходящей автономной системы и префикс сети источника и приёмника.
Изначально я хотел реализовать первую версию, так как она простая как три копейки, но впоследствии эта система будет интегрирована с Центром мониторинга и управления сетью связи общего пользования от Роскомнадзора. А для них есть необходимость передавать в Netflow трафике информацию о sampling interval и автономных системах. Возможно совсем в открытую я буду реализовывать первую версию или сразу два обработчика в одном скрипте. Так как первая версия меньше всего нагружает оборудование в случае если передавать все данные. Пока я не знаю, что делать в случае если будут приходить пакеты с использованием sampling interval, по идее нужно будет просто умножать полученные данные на этот показатель, но реальная точность получаемых данных уменьшится.
На текущий момент я занимаюсь теоретизированием и подотовкой к экспериментам. Надо подумать как хранить данные, взвесить все за и против. Так же надо принять решение о пробросе пакетов до следующего приёмника. Как минимум надо понять кто этим будет заниматься. Либо сам коллектор и тогда всё будет происходить on-line, либо написать дополнительную программу, которая будет читать полученные данные и по цепочке передавать информацию. Ещё я думаю о том, что при передаче информации в ЦМУССОП можно будет понизить sampling interval, чтобы не забивать исходящий канал на наших маршрутизаторах.
Надеюсь, что у меня получится реализовать все свои задумки. Хотя я понимаю, что Python не совсем подходящий инструмент для подобных целей.
Тэги: ИТ, Cisco, программирование, Питон
Отредактировано:2021-02-24 15:19:35