Skip to content

Latest commit

 

History

History
591 lines (452 loc) · 31.9 KB

EXTRA.ru.md

File metadata and controls

591 lines (452 loc) · 31.9 KB

Дополнительные возможности

GeoIP

Небольшое общее замечание: несмотря на то, что гео-информацией часто пользуются для разных отчетов, слишком доверять этим данным не стоит. Гео-базы не всегда точны, они не покрывают все адреса, при сетевых атаках часто используют поддельные IP адреса. Используйте GeoIP с разумной осторожностью.

Коллектор рассчитан на использоание GeoIP-баз в формате https://ipapi.is/geolocation.html

Для работы GeoIP нужно скачать эти данные, сконвертировать их во внутренний формат и и поместить в специальный каталог для коллектора.

Как это сделать, по пунктам:

Получаем и распаковываем CSV-файлы с данными:

$ mkdir geo && cd geo
$ wget https://ipapi.is/data/geolocationDatabaseIPv4.csv.zip
$ wget https://ipapi.is/data/geolocationDatabaseIPv6.csv.zip
$ unzip geolocationDatabaseIPv4.csv.zip
$ unzip geolocationDatabaseIPv6.csv.zip
$ cd ..

Строим из CSV-файлов базы во внутреннем формате. Для этого используется утилита xemkgeodb:

$ mkdir geodb
$ ./xemkgeodb -o geodb -v -t geo geo/geolocationDatabaseIPv4.csv geo/geolocationDatabaseIPv6.csv

После этого в каталоге geodb должны появиться файлы geo4.db и geo6.db.

Нужно поместить эти файлы в специальный каталог коллектора. Он задается в глобальном конфиге xenoeye.conf

"geodb": "/var/lib/xenoeye/geoip"
$ cp geodb/geo* /var/lib/xenoeye/geoip/

При перезапуске коллектора (или если послать процессу коллектора сигнал -HUP) он загрузит эти базы данных и начнут работать функции GeoIP.

Использование GeoIP в коллекторе

Данные GeoIP можно использовать в фильтрах (создавать географические объекты мониторинга) или в полях, которые экспортируются в PostgreSQL

Это делается с помощью функций:

  • continent() - двухбуквенный код континента в нижнем регистре (eu, as, ...)
  • country_code() - двухбуквенный код страны в нижнем регистре (es, ru, cn, ...)
  • country() - полное название страны
  • state() - штат (область)
  • city() - город
  • zip() - индекс
  • lat() - широта
  • long() - долгота

Все функции принимают в качестве аргумента netflow-поле с IP-адресом.

Например, для того чтобы создать объект мониторинга, в котором будет трафик, входящий в нашу сеть и только из России, нужно сделать такой фильтр:

ingress_ru/mo.conf:

{
	"filter": "dst host our-net and country(src host) 'ru'"
	/* ... */
}

Коллектор будет преобразовывать country(src host) каждого флова в двухбуквенный код страны и сравнивать его с ru

Для того, чтобы экспортировать гео-информацию в СУБД, нужно в качестве поля использовать функцию из списка выше.

Примеры:

Объект мониторинга ingress, в него попадает весь входящий в наши сети трафик, src-адрес преобразовывается в название страны, по каждой стране суммируются октеты и экспортируются в СУБД: ingress/mo.conf

{
	"filter": "dst net our-net",

	/* ... */

	"fwm": [
		/* ... */
		{
			"name": "country",
			"fields": ["octets desc", "country(src host)"]
		}
	]
}

Данные по странам будут экспортироваться в СУБД в таком виде:

=> select * from ingress_country limit 10;
          time          |   octets    | country_src_host_ 
------------------------+-------------+-------------------
 2023-10-12 11:02:45+03 | 17561134000 | Russia
 2023-10-12 11:02:45+03 |  3002667000 | ?
 2023-10-12 11:02:45+03 |  2094074500 | United States
 2023-10-12 11:02:45+03 |  2030411000 | Netherlands
 2023-10-12 11:02:45+03 |   403552000 | Germany
 2023-10-12 11:02:45+03 |   376779000 | Finland
 2023-10-12 11:02:45+03 |   144323000 | France
 2023-10-12 11:02:45+03 |   128383500 | Japan
 2023-10-12 11:02:45+03 |   124174500 | Hungary
 2023-10-12 11:02:45+03 |    61062500 | United Kingdom

? означает что для этих адресов нет записей в базе GeoIP

Еще один пример. Объект мониторинга ingress_ru, трафик в наши сети только из России. src-адреса преобразовываются в название областей, городов, по каждому элементу суммируются октеты и экспортируются в СУБД. ingress_ru/mo.conf:

{
	"filter": "dst net our-net and country_code(src host) 'ru'",

	/* ... */
	"fwm": [
		{
			"name": "city",
			"fields": ["octets desc", "city(src host)"]
		}
		,
		{
			"name": "state",
			"fields": ["octets desc", "state(src host)"]
		}
		/* ... */
	]
}

Данные, которые экспортируются в СУБД:

=> select * from ingress_ru_state;
          time          |   octets   |    state_src_host_    
------------------------+------------+-----------------------
 2023-10-12 22:06:34+03 | 6571390500 | Москва
 2023-10-12 22:06:34+03 | 2879552500 | Санкт-Петербург
 2023-10-12 22:06:34+03 | 2359202000 | Ленинградская Область
 2023-10-12 22:06:34+03 |  665152000 | Архангельская Область
 2023-10-12 22:06:34+03 |  374177500 | Тюменская Область
 2023-10-12 22:06:34+03 |  354527500 | Владимирская Область
 2023-10-12 22:06:34+03 |  321759000 | Костромская Область
 2023-10-12 22:06:34+03 |  131455000 | Калужская Область
 2023-10-12 22:06:34+03 |   29730000 | Рязанская Область
...


=> select * from ingress_ru_city;
          time          |   octets   |  city_src_host_  
------------------------+------------+------------------
 2023-10-12 22:06:34+03 | 6569109000 | Moscow
 2023-10-12 22:06:34+03 | 2879552500 | Saint Petersburg
...

Автономные системы

Даже если роутер не может экспортировать номера AS, их можно получить в коллекторе из IP адресов с помощью внешних баз. Кроме номеров, можно получить и названия AS.

Мы используем базы данных проекта https://github.com/sapics/ip-location-db

Это работает приблизительно так же, как и c GeoIP базами.

Нужно скачать csv-файлы с данными:

$ cd geo
$ wget https://raw.githubusercontent.com/sapics/ip-location-db/main/asn/asn-ipv4.csv
$ wget https://raw.githubusercontent.com/sapics/ip-location-db/main/asn/asn-ipv6.csv
$ cd ..

Сконвертировать во внутренний формат:

$ ./xemkgeodb -o geodb -t as geo/asn-ipv4.csv geo/asn-ipv6.csv

Если все прошло без ошибок, скопировать базы в каталог коллектора:

$ cp geodb/as* /var/lib/xenoeye/geoip/

После перезапуска коллектора (или если послать процессу коллектора сигнал -HUP), базой можно будет пользоваться.

Функции AS

  • asn() - номер автономной системы
  • asd() - текстовое описание автономной системы

Так же, как и GeoIP-функции, они принимают в качестве аргумента netflow-поле с IP-адресом.

Пример, разбиваем весь входящий трафик по названиям автономных систем: ingress/mo.conf:

{
	"filter": "dst net our-net",

	/* ... */

	"fwm": [
		/* ... */
		{
			"name": "as",
			"fields": ["octets desc", "asd(src host)"],
			"limit": 30
		}
	]
}
=> select * from ingress_as;
          time          |   octets   |      asd_src_host_       
------------------------+------------+--------------------------
 2023-10-13 11:40:46+03 | 7260510500 | PJSC MegaFon
 2023-10-13 11:40:46+03 | 3816886000 | T2 Mobile LLC
 2023-10-13 11:40:46+03 | 2124086000 | PJSC Rostelecom
 2023-10-13 11:40:46+03 | 1551007000 | Google LLC
 2023-10-13 11:40:46+03 | 1361819000 | LLC VK
 2023-10-13 11:40:46+03 |  777337000 | CJSC RASCOM
 2023-10-13 11:40:46+03 |  761207000 | Global DC Oy
 2023-10-13 11:40:46+03 |  753907500 | Hetzner Online GmbH
 2023-10-13 11:40:46+03 |  592446000 | MEGASVYAZ LLC
...

Обновление баз без перезапуска коллектора

GeoIP базы и базы AS обновляются достаточно часто. Будьте внимательны: владельцы этих баз могут молча изменить формат CSV-файлов и тогда коллектор не сможет их прочитать.

Лучше обновлять GeoIP базы вручную, или как-то контролировать процесс обновления. Алгоритм обновления приблизительно такой же, как и для первого использования.

  1. Нужно скачать CSV-файлы
  2. Сгенерировать .db-файлы из CVS-файлов
  3. Посмотреть, все ли прошло нормально, нет ли ошибок
  4. Поместить новые .db-файлы в geoip-каталог коллектора
  5. Послать процессу коллектора сигнал -HUP, он перечитает базу

Утилита xegeoq

Для тестировании GeoIP и AS мы сделали утилиту xegeoq.

Ее можно использовать для получения GeoIP-информации и информации об AS.

Утилита принимает на вход путь к базам (во внутреннем формате) и IP-адрес или список IP-адресов. Адреса могут быть как IPv4, так и IPv6

./xegeoq -i /var/lib/xenoeye/geoip 1.1.1.1 2A03:2880:10FF:0008:0000:0000:FACE:B00C
1.1.1.1 geo: oc, au, Australia, Victoria, Research, 3095, -37.7, 145.18333
1.1.1.1 as: 13335, Cloudflare, Inc.
2A03:2880:10FF:0008:0000:0000:FACE:B00C geo: ?
2A03:2880:10FF:0008:0000:0000:FACE:B00C as: 32934, Facebook, Inc.

Визуализация GeoIP-данных и названий AS с помощью Grafana

Для того, чтобы было проще делать отчеты в Графане, мы написали такую PL/PGSQL функцию:

CREATE OR REPLACE FUNCTION xe_rep(
        src TEXT,
        fld TEXT,
        aggr_fld TEXT,
        k TEXT,
        cond TEXT,
        ntop INT DEFAULT 20,
        unk TEXT DEFAULT '?'
    ) RETURNS TABLE (
        tm TIMESTAMPTZ, 
        val BIGINT,
        name TEXT
    ) AS $$
DECLARE
    query TEXT;
    select_top TEXT;
    fld_t TEXT;
BEGIN
    fld_t := fld || '::text';

    select_top := 'SELECT
            sum('|| aggr_fld || ') AS val, COALESCE (' || fld_t || ', ''Other'') AS name
        FROM ' || src || 
        ' WHERE ' || cond || ' GROUP BY name ORDER BY val desc limit ' || ntop;

    query := 'SELECT time, (sum(' || aggr_fld || ')' || k || ')::bigint AS val, COALESCE(NULLIF(name, ''''), ''' || unk || ''')
            FROM (
                WITH topval AS (' || select_top || ')
            SELECT time, ' || aggr_fld || ',  COALESCE (' || fld_t || ', ''Other'') AS name
                FROM ' || src || ' WHERE ' || cond || ' AND ' || fld_t || ' IN (SELECT name from topval)
            UNION
            SELECT time, ' || aggr_fld || ', ''Other'' AS name
                FROM ' || src || '
                WHERE ' || cond || ' AND ' || fld_t || ' NOT IN (SELECT name from topval)
            UNION
            SELECT time, ' || aggr_fld || ', ''Other'' AS name
                FROM ' || src || '
                WHERE ' || cond || ' AND ' || fld_t || ' IS NULL
        ) AS report
    GROUP BY time, name ORDER BY time';

    RETURN QUERY EXECUTE query;
END;
$$ LANGUAGE plpgsql;

Функция строит топ-N сущностей (стран, городов, IP-адресов и т.п.) за период и отбирает только их. Те, кто не попал в топ, группируются под названием 'Other'.

При создании панели в Графане в поле для SQL запроса можно написать вызов этой функции:

select tm as time, val as city, name from xe_rep('ingress_ru_city', 'city_src_host_', 'octets', '*8/30', $$ $__timeFilter(time) $$, 20);
  • ingress_ru_city - таблица с трафиком по городам
  • city_src_host_ - название поля с городами в таблице
  • octets - отчет по байтам (не по пакетам)
  • *8/30 - коэффициент, в таблицу добавляются данные каждые 30 секунд, чтобы получить биты в секунду, уможаем байты на 8 и делим на 30 секунд.
  • $$ $__timeFilter(time) $$ - макрос Графаны, фильтрует данные только за нужный период
  • 20 - отбирается топ-20 городов по количеству трафика, остальные будут в отчете как 'Other'

Входящий трафик с разбивкой по автономным системам:

Grafana by as

Входящий трафик по странам:

Grafana geoip by countries

Входящий трафик только из России по регионам:

Grafana geoip by state

Входящий трафик только из России по городам:

Grafana geoip by city

Классификация трафика

Часто сетевым инженерам бывает нужно понять хотя бы в общих чертах, трафик каких типов доминирует в сети. Это делают с помощью "классификации по приложениям".

Данные из netflow достаточно сложно классифицировать именно "по приложениям" и этому есть несколько причин:

  • Даже обычные пользователи (не злоумышленники) запускают сервисы на нестандартных и высоких портах для того чтобы их не заметили сканирующие роботы и системы мониторинга
  • В одном флове могут передаваться данные о нескольких сетевых пакетах, мы можем посчитать только средний размер пакета
  • Поле TCP-flags - это на самом деле комбинация (логическое ИЛИ) TCP-флагов нескольких пакетов TCP-сессии, которые увидел роутер
  • Netflow данные в большинстве случаев семплированные

То есть классификация "по приложениям" в случае с netflow/IPFIX это скорее классификация по некоторым netflow-полям.

Обычно для классификации используют порты, протоколы, TCP-флаги и размеры пакетов.

Классификация в xenoeye работает так: пользователь выбирает поля, по которым хочет классифицировать трафик. Коллектор некоторое время собирает фловы, агрегирует их, потом сортирует по убыванию пакетов/октетов, отбирает верхние X процентов (число задается пользователем) и разбивает этот трафик на "классы". После этого к каждому флову добавляется метка - название класса. Коллектор пытается назвать классы человеко-читаемо, например преобразовывает номера портов в названия, комбинации TCP флагов в текстовый вид ("ACK+PSH+SYN", "ACK+RST" и т.д.). Названия классов хранятся в файлах (/var/lib/xenoeye/clsf/<название объекта>/<номер классификатора>/<клаcc>/name), можно переименовать любой класс. Скажем, назвать UDP трафик на 443 порту как "QUIC/VPN". Классификация происходит постоянно при работе коллектора. Так как сетевой трафик может сильно изменяться с течением времени, периодически могут добавляться новые классы.

Делая этот модуль, планировалось решить несколько задач:

  • видеть, что происходит в сети в разрезе сервисов
  • иметь возможность объединять под общим названием некторые виды трафика
  • иметь возможность видеть раздельно некоторые виды трафика (например, трафик по одним портам/протоколам, но с разными размерами пакетов)
  • так как классы создаются автоматически из топ-трафика, после некоторого периода "обучения" появление нового класса можно рассматривать как сетевую аномалию

Для классификации в коллекторе есть такие вспомогательные функции:

  • min(port1, port2) - выбирает минимальное значение из port1 и port2
  • mfreq(port1, port2) - выбирает более часто используемый порт
  • div(aggr1, aggr2) - обычное деление, используется для определения среднего размера пакета
  • divr(aggr1, aggr2, N) - деление c округлением
  • divl(aggr1, aggr2, N) - деление c округлением вниз к целой степени N

min(src port, dst port) - минимальное значение из двух портов. Если сервисы работают на небольших номерах портов, эта функция вернет "серверный" порт, по которому можно угадать тип трафика

mfreq(src port, dst port) - возвращает порт, который используется чаще (статистика собирается только для текущего объекта мониторинга). Если сервис находится на очень высоком порту, но он часто попадается в фловах, то функция вернет этот более часто используемый высокий порт

Функции div* предназначены для классификации по средним размерам пакетов

divr(octets,packets,N) - деление с округлением. Делит количество байт в флове на количество пакетов и округляет

divr(octets,packets,100) для размеров пакетов из диапазона 0-99 будет возвращать 0, из диапазона 100-199 -> 100, 200-299 -> 200 и т.д.

divl(octets,packets,N) - деление с окpуглением вниз к целой степени N. Если нужно грубо классифицировать по размеру пакетов, например как "маленькие", "средние", "большие" - можно использовать эту функцию.

divl(octets,packets,10) для размеров пакетов из диапазона 10-99 будет возвращать 10, из диапазона 100-999 -> 100, 1000-9999 -> 1000

Один объект мониторинга можно классифицировать с разными наборами полей. Названия классов добавляются к набору полей фловов как "class0", "class1" и т.д. Ниже приведен пример для классификации всего входящего трафика, но можно классифицировать произвольные объекты мониторинга. Например DNS-трафик по протоколам (UDP/TCP) и размеру пакетов или отдельно HTTPS по протоколам и TCP-флагам. ingress/mo.conf:

{
	"filter": "dst net our-net",                             // весь входящий трафик

	"classification": [{
		// class0
		"fields": ["proto", "mfreq(src port,dst port)"], // нас интересуют протоколы и порты
		"top-percents": 90,                              // классифицируем верхние 90% трафика
		"val": "octets desc"                             // 90% отбираются по количеству октетов
	}
	,
	{
		// class1
		"fields": ["proto", "div_r(octets,packets,100)"], // протокол + размер пакетов
		"top-percents": 90,
		"val": "packets desc"                             // 90% отбираются по количеству пакетов
	}
	,
	{
		"fields": ["proto", "tcp-flags"],                 // протокол + tcp флаги (для не-tcp поле флагов == 0)
		"top-percents": 90,
		"val": "octets desc"
	}
	],

	/* ... */
	"fwm": [
		/* ... */
		/* экспортируем в PostgreSQL классифицированный по разным полям трафик */
		{
			"name": "clsf_port",
			"fields": ["octets desc", "class0"],
			"limit": 30
		}
		,
		{
			"name": "clsf_size",
			"fields": ["packets desc", "class1"]
		}
		,
		{
			"name": "clsf_flags",
			"fields": ["octets desc", "class2"]
		}
	}
}

Результаты классификации на графиках Графаны

Для построения временных рядов с классификацией можно воспользоваться функцией xe_rep, которая показана выше:

Параметры вызова те же - название таблицы, поле и т.д.

По умолчанию неклассифицированный трафик будет показан с названием ?. Чтобы это изменить, нужно добавить опциональный параметр:

select tm as time, val as class, name from xe_rep('ingress_clsf_port', 'class0', 'octets', '*8/30', $$ $__timeFilter(time) $$, 20, 'Unclassified');

Классификация по портам:

Grafana classification by ports

По размеру пакетов:

Grafana classification by size

Классификация отдельно HTTP/HTTPS трафика по протоколам и TCP флагам

Grafana classification by size

sFlow

Коллектор собирает и обрабатывает sFlow, если в главном конфигурационном файле есть секция "sflow-capture":

	"sflow-capture": [
		//{"pcap": {"interface": "eth0", "filter": "udp and port 6343"}},
		{"socket": {"listen-on": "*", "port": "6343"}}
	]

Так же, как и для Netflow, можно использовать обычные сокеты или собирать с интерфейса с помощью libpcap.

После того, как коллектор начнет собирать sFlow, его можно обрабатывать точно так же, как и Netflow. Можно создавать объекты мониторинга, описывать фильтр, таблицы для экспорта данных и скользящие средние.

Если коллектор не понимает пакет sFlow, то он его молча отбрасывает. Чтобы понимать, как коллектор видит sFlow-трафик, в комплекте есть утилита xesflow. Она захватывает трафик с помощью pcap и показывает известные ей sFlow-поля.

# ./xesflow -i eth1 -f "udp and port 6343"
version: 5 [sflow-impl.h, line 198, function sflow_process()]
agent address type: 1 [sflow-impl.h, line 205, function sflow_process()]
agent address (IPv4): 172.16.2.2 [sflow-impl.h, line 214, function sflow_process()]
agent id: 16 [sflow-impl.h, line 232, function sflow_process()]
sequence: 15690 [sflow-impl.h, line 235, function sflow_process()]
uptime: 2858088699 [sflow-impl.h, line 238, function sflow_process()]
samples: 7 [sflow-impl.h, line 241, function sflow_process()]
        sample #0 [sflow-impl.h, line 245, function sflow_process()] 
        sample type: 1 (SF5_SAMPLE_FLOW) [sflow-impl.h, line 249, function sflow_process()]
        length:  144 [sflow-impl.h, line 61, function sf5_flow()]
        sequence: 53379644 [sflow-impl.h, line 64, function sf5_flow()]
        src id: 518 [sflow-impl.h, line 67, function sf5_flow()]
        sampling rate: 400 [sflow-impl.h, line 70, function sf5_flow()]
        sample pool: 1956205512 [sflow-impl.h, line 74, function sf5_flow()]
        drop events: 0 [sflow-impl.h, line 76, function sf5_flow()]
        input interface: 0 [sflow-impl.h, line 80, function sf5_flow()]
        output interface: 518 [sflow-impl.h, line 88, function sf5_flow()]
        number of elements: 2 [sflow-impl.h, line 95, function sf5_flow()]
                element #0 [sflow-impl.h, line 100, function sf5_flow()]
                tag: 1 [sflow-impl.h, line 102, function sf5_flow()]
                element length: 80 bytes [sflow-impl.h, line 105, function sf5_flow()]
                header protocol: 1 [sflow-impl.h, line 126, function sf5_flow()]
                header len: 64 [sflow-impl.h, line 127, function sf5_flow()]
                sampled size: 68 [sflow-impl.h, line 129, function sf5_flow()]
                        Ethernet src: 54:4b:8c:ef:23:c0 [rawparse.h, line 116, function rawpacket_parse()]
                        Ethernet dst: 00:25:90:7c:41:8f [rawparse.h, line 116, function rawpacket_parse()]
                        Ethernet proto: 0x8100 [rawparse.h, line 116, function rawpacket_parse()]
                        VLAN 607 [rawparse.h, line 129, function rawpacket_parse()] 
                        IPv4 src: 91.32.91.80 [rawparse.h, line 179, function rawpacket_parse()]
                        IPv4 dst: 121.101.245.97 [rawparse.h, line 179, function rawpacket_parse()]
                        TOS: 0x0 [rawparse.h, line 179, function rawpacket_parse()]
                        ID: 16183 [rawparse.h, line 179, function rawpacket_parse()]
                        TTL: 118 [rawparse.h, line 179, function rawpacket_parse()]
                        IP protocol: 6 [rawparse.h, line 179, function rawpacket_parse()]
                        TCP src port: 2872 [rawparse.h, line 253, function rawpacket_parse()]
                        TCP dst port: 443 [rawparse.h, line 253, function rawpacket_parse()]
                        TCP flags: 0x10 [rawparse.h, line 253, function rawpacket_parse()]
...

Дополнительный анализ данных с помощью sFlow: DNS и SNI

Так как sFlow агент посылает в коллектор куски пакетов, их можно парсить и получать некоторую дополнительную информацию.

В коллекторе есть парсеры протоколов DNS и TLS (HTTPS) SNI.

Например, если вы хостер, то эти парсеры могут помочь составить "карту хостинга", чтобы понимать какие домены хостятся на вашей площадке.

	"fwm": [
		// ...
		{
			"name": "dns",
			"fields": ["dns-name", "dns-ips"]
		}
		,
		{
			"name": "sni",
			"fields": ["src host", "dst host", "sni"]
		}
	]

Коллектор парсит A(IPv4) и AAAA(IPv6) DNS-записи.

dns-ips сохраняются в виде {ip1, ip2, ...} - в пакете с DNS-ответом может быть несколько IP-адресов.

Запрос к СУБД для получения доменных имен и их адресов может выглядеть приблизительно так:

=> select distinct dns_name, unnest(dns_ips::inet[]) as ip from all_dns_sni_d order by ip;
 ns4-34.azure-dns.info.                      | 13.107.206.34
 ns3-34.azure-dns.org.                       | 13.107.222.34
 144.240.101.34.bc.googleusercontent.com.    | 34.101.240.144
 connectivity-check.ubuntu.com.              | 91.189.91.49
 connectivity-check.ubuntu.com.              | 185.125.190.18
 connectivity-check.ubuntu.com.              | 2001:67c:1562::24
 ns3-39.azure-dns.org.                       | 2a01:111:4000:10::27
 mirror.docker.ru.                           | 2a04:8580:ffff:fffe::2
...

Для получения доменных имен из SNI размер захватываемых пакетов должен быть достаточно большим.