aboutsummaryrefslogtreecommitdiff

ifstat

Эта программа обрабатывает пакеты с сетевого интерфейса, собирает статистику (количество полученных пакетов, количество полученных байт, распределение количества пакетов по их размерам) о трафике установленного вида и отправляет броадкастом полученную статистику по ubus.

Пример вывода утилиты ifstat

Demo

Зависимости

  • актуальное ядро Linux с поддержкой eBPF и XDP (проверено на 4.18);
  • ubox и ubus с поддержкой Lua;
  • luajit;
  • libbcc.

TODO

  1. Завести автоматические тесты с запуском tcpreplay, созданием виртуального сетевого интерфейса и валидацией полученной статистики;

  2. Провести ручное нагрузочное тестирование на реальном железе с Debian и посмотреть на производительность;

  3. Перейти с lua-based конфига на что-то, что можно адекватно валидировать (ini/yaml/toml).

Установка

Целевая система: Debian 9 (stretch), amd64

Команды установки и запуска проверены вручную через Vagrant (образ debian/stretch64)

echo "deb http://deb.debian.org/debian stretch-backports main contrib non-free" | sudo tee /etc/apt/sources.list.d/backports.list && \
sudo apt-get update && \
sudo apt-get install -y linux-image-4.18.0-0.bpo.1-amd64 \
                     linux-headers-4.18.0-0.bpo.1-amd64 \
                     linux-compiler-gcc-6-x86=4.18.6-1~bpo9+1 \
                     luajit libjson-c3 git && \
wget http://tmp.nazaryev.ru/ifstat/{libbcc_0.7.0-1_all.deb,ubox-0.1.1-Linux.deb,ubus-0.1.1-Linux.deb} && \
sudo dpkg -i libbcc_0.7.0-1_all.deb ubox-0.1.1-Linux.deb ubus-0.1.1-Linux.deb && \
rm -f libbcc_0.7.0-1_all.deb ubox-0.1.1-Linux.deb ubus-0.1.1-Linux.deb && \
git clone https://github.com/3ap/ifstat && cd ifstat && \
git submodule init && \
git submodule update

sudo reboot # для перехода на новую версию ядра

Запуск и конфигурация

cd ifstat
vi config.lua      # настройка фильтров (формат см. ниже)
sudo /usr/sbin/ubusd &
sudo ./ifstatd.lua # запуск сервера (компиляция, инъекция eBPF, отправка)
sudo ./ifstat.lua  # запуск клиента (отображение статистики от сервера)

В качестве конфига используется находящийся рядом с ifstatd.lua файл config.lua:

local _config = {
  delay_ms = 500,
  iface = "enp0s8",

  filters = {
    {
      filter_num = 0,            -- номер фильтра (от 0 до 4 включительно)
      enabled = 1,               -- включен (1) / выключен (0)
      ipproto = IPPROTO_TCP,     -- IPPROTO_TCP, IPPROTO_UDP или ANY
      src_ip = "140.82.33.182",  -- строка с IPv4 или ANY
      dst_ip = ANY,              -- аналогично src_ip
      src_port = 22,             -- номер UDP/TCP-порта или ANY
      dst_port = ANY             -- аналогично src_port
    },
    {
      filter_num = 1,
      enabled = 0,
    },
    ...
}

Архитектура

Для того, чтобы собирать статистику максимально эффективно, используется интерфейс ядра eBPF. Этот интерфейс даёт возможность встроить код прямо в ядро, который будет подсчитывать необходимую статистику. Кроме того, для наибольшей производительности eBPF вешается на XDP-хук (eXpress Data Path), что позволяет получить доступ до пакета даже раньше, чем он будет обработан сетевым стеком ядра.

Основной код, задействованный в фильтрации и подсчёте статистики, написан на Си (ifstat_kern.c). Процессом компиляции и инъекцией этого кода в ядро занимается lua-скрипт, основанный на официальных байндингах проекта bcc (это фреймворк упрощает процесс создания утилит, использующих eBPF).

Почему я выбрал Lua в качестве вспомогательного языка?

  1. Весь критичный к производительности код запускается в ядре и написан на Си, всё остальное (парсинг конфигов и командной строки, инъекция eBPF, вычитка готовых данных, отправка данных по ubus) можно написать на любом подходящем для этих задач языке;

  2. Проект bcc официально поддерживает нативный интерфейс (библиотеку libbcc) для инъекции eBPF в ядро и байндинги к нему поддерживаются непосредственно разработчиком только для Lua и Python -- выбор сильно сужается;

  3. Проект ubus предоставляет только нативный интерфейс и Lua-байндинги;

  4. Идея написать с нуля и поддерживать байндинги для bcc или ubus для других языков откидывается сразу.

Таким образом на выбор остаются только Си и Lua. Писать на Lua обработку конфигов и сериализацию/десериализацию данных значительно проще и приятнее, чем на Си, поэтому Lua и был выбран.