Всем привет! Меня зовут Тимур, в компании YADRO я разрабатываю ПО для коммутаторов KORNFELD. Однажды на работе мне потребовалось написать программу для включения прослушивания интерфейсов, которые удовлетворяют определенной конфигурации системы. Старшие коллеги сказали, что это можно сделать с помощью netlink. Я начал разбираться в этой технологии с нуля, потратил больше двух месяцев на изучение протокола, написание обработчика и хочу поделиться своим опытом. 

Я постараюсь не просто осветить теорию и представить готовый код, а показать, как с netlink можно сделать именно то, что вам нужно. Как узнать, что именно делает система при настройке сетевых интерфейсов и как обрабатывать ее команды. Это важно, поскольку из множества сообщений от netlink для решения конкретной задачи обычно нужны лишь некоторые.

Что такое netlink

Для ответа посмотрим manpage arch wiki:

Протокол netlink используется для передачи информации между ядром и процессами в пользовательском пространстве. Он состоит из стандартного, основанного на сокетах интерфейса для процессов пользователя и внутреннего API ядра, предназначенного для модулей ядра. Внутренний интерфейс ядра в этой странице не описан. Кроме того, существует устаревший интерфейс netlink, работающий через символьные устройства netlink. Этот интерфейс здесь также не описан; он предназначен только для обратной совместимости. Netlink обеспечивает для приложений сервис передачи датаграмм. В качестве socket_type могут использоваться типы сокетов как SOCK_RAW, так и SOCK_DGRAM. Несмотря на это, протокол netlink не различает датаграммные и неструктурированные (raw) сокеты.

Если простыми словами, то netlink — это подсистема ядра linux, отвечающая за взаимодействие процессов системы как из user space, так и из kernel space. Подсистема построена на базе стандартных BSD-сокетов и работать с ней можно как с обычными сокетами. Netlink обеспечивает связь разных сервисов и систем, но нас интересует только состояние системных интерфейсов, так что мы должны выбрать правильное семейство. Оно указывается при создании сокета netlink.

int netlink_socket = socket(AF_NETLINK, socket_type, netlink_family);

Так как нас интересует именно конфигурация сетевых интерфейсов, нужно использовать семейство NETLINK_ROUTE, которое принадлежит rtnetlink. Посмотрим в arch wiki, что такое rtnetlink:

Rtnetlink позволяет читать и изменять таблицы маршрутизации ядра. Он используется для взаимодействия различных подсистем внутри ядра, а также для взаимодействия пользовательских программ. Сетевыми маршрутами, IP-адресами, параметрами связи (link parameters), настройками соседства (neighbor setups), алгоритмами планирования очереди (queueing disciplines), классификацией трафика и пакетными классификаторами можно управлять через сокеты NETLINK_ROUTE. Они основываются на сообщениях netlink.

Получается, что netlink — это метод для взаимодействия процессов, а непосредственно за взаимодействие с интерфейсами отвечает rtnetlink. Далее для упрощения под netlink мы будем подразумевать только работу с rtnetlink.

Доступ к netlink осуществляется через специальный тип сокетов. В netlink сокет можно писать и из него можно читать. Сразу уточню, что этот тип сокетов не потоковый, поэтому читать по одному байтику не выйдет. Когда вы пользуетесь стандартными утилитами linux, например iproute или nft, вы на самом деле взаимодействуете с netlink.

Как я изучал netlink

В интернете я нашел всего одну статью, где описывали монитор сетевых портов. Это был хороший старт, но для решения моей задачи информации оказалось недостаточно. Уже во время написания этой статьи я нашел еще пару материалов, которые, как я рассчитываю, хорошо дополнят мой. Помимо статей, нашел несколько man-страниц, в которых описаны пакеты, параметры, структуры и даже есть примеры кода. Оттуда я взял готовый код, способный получать информацию о появлении нового интерфейса и добавлении его в vlan. Но задача требовала отслеживать намного больше параметров системы.

Следующая остановка — пакет iproute2. Iproute распространяется под GNU GPL 2, так что я скачал исходники, собрал их и начал разбираться в коде на C. Чтобы проще понимать логику, я удалял некоторые участки кода, в которые при вызове моей функции программа не входила. Затем пересобирал программу и продолжал изучение и трассировку. Так я понял общий принцип обработки пакетов и сделал обработку пары дополнительных параметров. Но оставалось еще много других условий.

В какой-то момент я вспомнил про утилиту strace: с ней стало проще изучать код и ориентироваться в нем. Strace позволяла увидеть финальный этап работы функции и все ее параметры. Решая свои задачи, я копировал и адаптировал код из iproute. Я не особо вникал во внутреннее устройство протокола, но с каждым этапом разработки это становилось все сложнее и сложнее.

Наконец, я сделал обработку всех параметров кроме одного. В функции его обработки в iproute вызывалась обработка какого-то системного файла, и это было очень странно, так как я видел этот параметр в сообщении netlink через strace. Я зашел в тупик. Код выглядел очень страшно и совсем не нравился мне. Покопавшись в этом еще один день, я понял, что так продолжаться не может. Я решил переписать все заново без использования кода из iproute. Хотел сделать код красивым и максимально понятным для тех, кто будет читать его после меня.

Описание протокола

Netlink-сообщения используют 4-байтное выравнивание.

Заголовок Netlink (nlmsghdr)

В заголовке хранится информация о длине, размере и типе сообщения.

0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                          Length                             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|            Type              |           Flags              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                      Sequence Number                        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                      Process ID (PID)                       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Разберем структуру заголовка:

  • Length — 4 байта (32 бита). Здесь описана длина всего сообщения, включая заголовок, порядок байт от младшего к старшему. В коде называется nlmsg_len.

  • Type — 2 байта (16 бит). Тип сообщения.

  • Флаги, nlmsg_flags — 2 байта (16 бит).

  • Номер в последовательности. nlmsg_seq — 4 байта (32 бита).

  • Идентификатор процесса — 4 байта (32 бита).

Заголовок в сумме занимает 16 байт и может быть описан структурой.

/// \brief Struct nlmsghdr - fixed format metadata header of Netlink messages
struct nlmsghdr 
{
    /// \brief Length of message including header
    std::uint32_t nlmsg_len = 0;                        // 4 bytes
	
    /// \brief Message content type
    std::uint16_t nlmsg_type = 0;                       // 2 bytes
	
    /// \brief Additional flags
    std::uint16_t nlmsg_flags = 0;                      // 2 bytes
	
    /// \brief Sequence number
    std::uint32_t nlmsg_seq = 0;                        // 4 bytes
	
    /// \brief Sending process port ID
    std::uint32_t nlmsg_pid = 0;                        // 4 bytes
};                                                      // 16 bytes

Поле nlmsg_type может принимать значения netlink и конкретного протокола — например, rtnetlink. Значения, общие для netlink:

  • NLMSG_NOOP — сообщения такого типа игнорируются.

  • NLMSG_ERROR — сообщение с ошибкой.

  • NLMSG_DONE — сообщение с этим флагом должно завершать сообщение, разбитое на несколько частей.

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

  • RTM_NEWLINK — сообщение, в котором пришла информация о новом интерфейсе или об изменении старого.

  • RTM_DELLINK — сообщение с информацией об удалении интерфейса.

Поле nlmsg_flags может принимать значение NLM_F_MULTI, если информация не поместится в одно сообщение от netlink. Такое бывает, если у вас в системе много интерфейсов и вы отправляете запрос на получение их всех.

Особенности составных сообщений

Оценивая ситуацию из предыдущего абзаца, задаешься вопросом: а что вообще есть сообщение? Если взглянуть в rtnetlink.h на описание поля NLM_F_MULTI, то можно увидеть:

#define NLM_F_MULTI		0x02	/* Multipart message, terminated by NLMSG_DONE */

Получается, что в рамках одного multipart-сообщения может быть несколько сообщений, определяемых через nlmsghdr, но также к понятию сообщения обычно относятся и данные, полученные от сокета.

Взглянем на часть сообщения выше. В нем можно увидеть несколько nlmsg в одном сообщении от сокета. При этом в нем есть флаг NLM_F_MULTI — это означает, что netlink-сообщение не поместилось в одно сообщение от сокета. В итоге термин «сообщение» употребляется сразу в трех пересекающихся контекстах, nlmsghdr, NLM_F_MULTI и socket message, что весьма неудобно.

Большие сообщения netlink для нас неважны. Вся информация находится в nlmsghdr-сообщениях, которые не делятся между двумя сокетными сообщениями. Сокетное сообщение тоже неважно. Системный вызов recv сам возвращает количество прочитанных байт. Поскольку сокет не потоковый, он сам умеет разделять сокетные сообщения.

Nlmsghdr-сообщения не будут делиться между двумя сокетными сообщениями, так что можно обрабатывать их, не ожидая другое сокетное сообщение. Далее в статье под «сообщением» я буду иметь в виду только nlmsghdr.

Идентификация интерфейса (ifinfomsg)

После nlmsghdr идет ifinfomsg, где хранится информация для идентификации интерфейса.

0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|    Family     |   Pad byte    |            Type             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                            Index                            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                            Flags                            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                       Interface change                      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Разберем структуру:

  • Family, семейство сообщения — 1 байт (8 бит). Информация о необходимом типе сообщения будет получаться с помощью netlink. В коде называется ifi_family.

  • Pad byte — 1 байт (8 бит) заполнения. Нужен для выравнивания структуры к 16 байтам. В коде называется __ifi_pad.

  • Type, тип устройства — 2 байта (16 бит).

  • Index, индекс устройства системы — 4 байта (32 бита). Это главное значение для идентификации интерфейса, в коде называется ifi_index.

  • Flags, флаги устройства — 4 байта (32 бита). В коде называется ifi_flags, более подробно можно почитать в arch wiki.

  • Inteface change, маска изменения — 4 байта (32 бита).

На arch wiki или любом другом тематическом ресурсе можно обнаружить такой текст: «элемент ifi_change зарезервирован на будущее, и его значение всегда должно быть равно 0xFFFFFFFF». Однако, если посмотреть strace, можно заметить, что это не соответствует действительности.

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

Изменение этого интерфейса:  

Добавление интерфейса после запуска программы:

Изменение этого интерфейса:

Для сигнализации об изменении интерфейса в обеих ситуациях использовалось значение 0x1
Для сигнализации об изменении интерфейса в обеих ситуациях использовалось значение 0x1

Всего ifinfomsg занимает 16 байт и может быть описано структурой:

/// \brief Struct ifinfomsg - passes link level specific information, not dependent on network protocol.
struct ifinfomsg 
{
    /// \brief Interface family
    std::uint8_t ifi_family = 0;                        // 1 byte

    /// \brief Pad byte
    std::uint8_t __ifi_pad = 0;                         // 1 byte

    /// \brief Interface type
    std::uint16_t ifi_type = 0;                         // 2 bytes

    /// \brief Interface index
    std::int32_t ifi_index = 0;                         // 4 bytes

    /// \brief Interface flags
    std::uint32_t ifi_flags = 0;                        // 4 bytes

    /// \brief Interface change mask
    std::uint32_t ifi_change = 0;                       // 4 bytes
};                                                      // 16 bytes

Описание атрибутов (rtattr)

Вся информация об интерфейсе передается с помощью атрибутов, каждый из которых описывается через структуру rtattr. В моей реализации ее нет, но для полного понимания о ней нужно рассказать.

Возьмем определение структуры из rtnetlink.h.

struct rtattr
{
	unsigned short	rta_len;
	unsigned short	rta_type;
};

Структура содержит два параметра. Первый, rta_len, хранит длину атрибута, включая четыре байта длины самой структуры rtattr. Второй, rta_type, хранит тип атрибута. Структура занимает четыре байта, а сразу за ней идет сам атрибут. Замечу, что данные имеют выравнивание по четыре байта, а rta_len не имеет выравнивания. Это важно помнить, так как следующий атрибут будет начинаться не через rta_len байт, а через «rta_len + паддинг до кратности 4» байт.

Демонстрация пакета

Для наглядной демонстрации netlink-сообщения я запустил программу с strace. Включил сохранение сообщений в дебаг-файл, а затем в рабочем докер-контейнере выполнил команду:

ip link add dev dum type dummy

Она создаст dummy-интерфейс с именем dum. Посмотрим, что нам посылает ядро и как это парсит strace:

Первые 16 байт сообщения — это заголовок, который хранится в nlmsghdr. Если перевести 1448 в шестнадцатеричную систему счисления, получится 5A8, а пакет начинается с A8 05. Не забываем, что порядок от младшего к старшему.

Далее идет 00 00, так как длина хранится в четырех байтах. Затем RTM_NEWLINK, имеющий значение 0x10 в hex или 16. Этот параметр хранится в двух байтах и поэтому после 0x10 идет 0x00. Остальные байты nlmsghdr — это нули.

Теперь переходим к ifinfomsg:

Нас интересует индекс интерфейса. Перед ним находится четыре байта — один на ifi_family, один на паддинг и два на ifi_type. Индекс интерфейса dum — 5, проверим это с помощью команды:

ip a show dum

Таким способом можно анализировать пакеты с данными, нужными в конкретной ситуации.

Подготовка среды разработки

Для разработки требуется плюсовый тулчейн, docker и strace. Будем работать на Linux, а тестировать с помощью Docker-контейнера. Он необязателен, но поможет избежать случайного удаления какого-нибудь важного сетевого интерфейса у хостовой системы. Из-за чего вполне может отвалиться интернет. Сборка пройдет на хостовой системе, для проверки работы программы будем использовать nsenter.

Склонируем репозиторий проекта:

git clone https://gitflic.ru/project/mrognor/netlink_client.git

Создадим образ для разработки:

docker build --tag 'netlink-dev-image' 

То же самое можно сделать с помощью скрипта create_image.sh. Эта команда создаст образ с именем netlink-dev-image из докер-файла в директории проекта. Это простой образ, основанный на Ubuntu, с установленным пакетом iproute2 для конфигурации сетевых интерфейсов.

Откроем отдельный терминал и запустим в нем образ: 

docker run --name netlink-dev-container -it --rm --privileged --mount src=./,target=/mnt/docker,type=bind netlink-dev-image:latest

То же самое можно сделать с помощью скрипта launch_container.sh. Команда запустит контейнер и примонтирует к нему текущую директорию в папку /mnt/docker. Монтирование нужно, чтобы удобно сохранять файлы, создаваемые в контейнере. После закрытия окна терминала контейнер будет удален.

Чтобы не настраивать в контейнере всю среду разработки и не устанавливать туда множество других пакетов, можно использовать утилиту nsenter. Она позволяет войти в сетевую систему контейнера, при этом все системные файлы и утилиты остаются доступными. Для работы с ней используйте скрипт to_docker_ns.sh. 

Для разработки я советую использовать два терминала: в одном можно запускать программу, а в другом управлять конфигурацией. Один терминал будет использоваться для запуска контейнера и последующей настройки системы. Во втором нужно будет перейти в сетевой namespace контейнера и запускать программу.

Все, что описано в статье, имеет смысл разбирать, только если вы собираетесь писать что-то сами. Так что я дополнительно покажу, как удобнее всего работать в моей любимой IDE. Для других сред разработки механизм будет аналогичен.

Первым делом откройте папку проекта в редакторе кода. Запустите в нем вкладку с терминалом и разделите ее на две. Все необходимые скрипты лежат в папке scripts. В первом терминале запустите контейнер с помощью команды 

launch_container.sh

Во втором терминале перейдите в namespace докер-контейнера командой

to_docker_ns.sh

Теперь в правом терминале вы можете настраивать сеть в докер-контейнере, а в левом у вас есть полноценная система со всеми пакетами и библиотеками. В левом терминале все сетевые интерфейсы будут взяты от контейнера, а не от вашей хостовой системы. Для проверки корректности работы можно добавить dummy-интерфейс в одном терминале, а затем проверить его добавление во втором. 

Пишем код

Теперь вы знаете, как устроен протокол и как парсить сообщения, так что приступим к коду. Для работы с netlink_client нужно использовать наследование от класса netlink_client. У него есть четыре чисто виртуальных метода, которые нужно определить в дочернем классе:

  • pre_epoll используется для вызова логики после создания и подготовки netlink-сокета и до начала поллинга. В него можно помещать запросы к netlink — например, запросить конфигурацию сетевых интерфейсов на момент запуска программы.

  • handle_rtm_newlink_message обрабатывает входящие сообщения с информацией о новых интерфейсах или изменениях старых.

  • handle_rtm_dellink_message обрабатывает входящие сообщения с информацией об удалении интерфейсов.

  • handle_nlmsg_error_message используется для обработки ошибок.

Также в классе есть метод iterate_netlink_message для обработки атрибутов сообщения. Он принимает три параметра:

  • msg_buf — указатель на байт, с которого начинаются атрибуты,

  • msg_len — количество байт с атрибутами, учитывая паддинг,

  • handle_function — функция для обработки атрибута.

Аргументы в функцию передаются из iterate_netlink_message. Она тоже принимает три аргумента:

  • attribute_len — длина атрибута без учета четырех байт длины и типа атрибута, то есть длина массива из третьего параметра,

  • attribute_type — тип атрибута,

  • attribute_data — указатель на байт, с которого начинается атрибут.

Класс netlink_client поддерживает режим отладки. Чтобы сохранить сообщение от netlink в бинарном виде, нужно в конце одного из трех определяемых пользователем методов обработки сообщения — handle_rtm_newlink_message, handle_rtm_dellink_message, handle_nlmsg_error_message — установить переменную is_debug как true. Тогда это сообщение будет сохранено в файл. С помощью debug_filename можно изменить название файла. В итоге эти две переменных позволяют очень гибко настроить дебаг определенных сообщений.

Для демонстрации работы я смоделировал три ситуации с обработкой разных конфигураций системы. В первом примере возьмем самую простую ситуацию. Во втором — более сложный пример с вложенными атрибутами. В третьем я продемонстрирую один подводный камень.

Отслеживаем добавление dummy-интерфейса

Здесь мы отследим появления интерфейса и узнаем его имя. Перейдем в директорию проекта и откроем два терминала. В одном запустим контейнер, а в другом с помощью скрипта зайдем в сетевое пространство этого контейнера.

Заменим содержимое файла main.cpp из директории src на следующий текст:

#include "netlink_client.h"

class local_nlclient : public netlink_client
{
public:
    virtual void pre_epoll() noexcept override {}

    virtual void handle_rtm_newlink_message(ifinfomsg& message_interface_info, char* data_buf, const std::uint32_t& data_len) noexcept override {}

    virtual void handle_rtm_dellink_message(ifinfomsg& message_interface_info, char* data_buf, const std::uint32_t& data_len) noexcept override {}

    virtual void handle_nlmsg_error_message(int error_code) noexcept override {}
};

int main()
{
    local_nlclient nlc;

    nlc.run();
}

Скомпилируем программу с помощью команды make и запустим через strace.

strace -o trace ./src/main

Добавим в контейнере dummy-интерфейс с именем dummy0:

ip link add dev dummy0 type dummy

После этого в файле trace будет находиться информация о пришедшем сообщении.

Находим функцию recvfrom и скопируем строчку с ней. Анализировать ее содержимое в исходном виде не очень удобно, поэтому я обычно форматирую на этом сайте. Создайте файл для анализа и вставьте форматированное сообщение в него.

Текст сообщения
recvfrom(3, [{
       nlmsg_len = 1452,
       nlmsg_type = RTM_NEWLINK,
       nlmsg_flags = 0,
       nlmsg_seq = 0,
       nlmsg_pid = 0
   }, {
       ifi_family = AF_UNSPEC,
       ifi_type = ARPHRD_ETHER,
       ifi_index = if_nametoindex("dummy0"),
       ifi_flags = IFF_BROADCAST | IFF_NOARP,
       ifi_change = 0xffffffff
   },
   [
       [{
           nla_len = 11,
           nla_type = IFLA_IFNAME
       }, "dummy0"],
       [{
           nla_len = 8,
           nla_type = IFLA_TXQLEN
       }, 1000],
       [{
           nla_len = 5,
           nla_type = IFLA_OPERSTATE
       }, 2],
       [{
           nla_len = 5,
           nla_type = IFLA_LINKMODE
       }, 0],
       [{
           nla_len = 8,
           nla_type = IFLA_MTU
       }, 1500],
       [{
           nla_len = 8,
           nla_type = IFLA_MIN_MTU
       }, 0],
       [{
           nla_len = 8,
           nla_type = IFLA_MAX_MTU
       }, 0],
       [{
           nla_len = 8,
           nla_type = IFLA_GROUP
       }, 0],
       [{
           nla_len = 8,
           nla_type = IFLA_PROMISCUITY
       }, 0],
       [{
           nla_len = 8,
           nla_type = IFLA_ALLMULTI
       }, 0],
       [{
           nla_len = 8,
           nla_type = IFLA_NUM_TX_QUEUES
       }, 1],
       [{
           nla_len = 8,
           nla_type = IFLA_GSO_MAX_SEGS
       }, 65535],
       [{
           nla_len = 8,
           nla_type = IFLA_GSO_MAX_SIZE
       }, 65536],
       [{
           nla_len = 8,
           nla_type = IFLA_GRO_MAX_SIZE
       }, 65536],
       [{
           nla_len = 8,
           nla_type = IFLA_GSO_IPV4_MAX_SIZE
       }, 65536],
       [{
           nla_len = 8,
           nla_type = IFLA_GRO_IPV4_MAX_SIZE
       }, 65536],
       [{
           nla_len = 8,
           nla_type = IFLA_TSO_MAX_SIZE
       }, 65536],
       [{
           nla_len = 8,
           nla_type = IFLA_TSO_MAX_SEGS
       }, 65535],
       [{
           nla_len = 8,
           nla_type = IFLA_NUM_RX_QUEUES
       }, 1],
       [{
           nla_len = 5,
           nla_type = IFLA_CARRIER
       }, 1],
       [{
           nla_len = 8,
           nla_type = IFLA_CARRIER_CHANGES
       }, 0],
       [{
           nla_len = 8,
           nla_type = IFLA_CARRIER_UP_COUNT
       }, 0],
       [{
           nla_len = 8,
           nla_type = IFLA_CARRIER_DOWN_COUNT
       }, 0],
       [{
           nla_len = 5,
           nla_type = IFLA_PROTO_DOWN
       }, 0],
       [{
           nla_len = 10,
           nla_type = IFLA_ADDRESS
       }, d2: a3: b4: a5: 65: e5],
       [{
           nla_len = 10,
           nla_type = IFLA_BROADCAST
       }, ff: ff: ff: ff: ff: ff],
       [{
           nla_len = 204,
           nla_type = IFLA_STATS64
       }, {
           rx_packets = 0,
           tx_packets = 0,
           rx_bytes = 0,
           tx_bytes = 0,
           rx_errors = 0,
           tx_errors = 0,
           rx_dropped = 0,
           tx_dropped = 0,
           multicast = 0,
           collisions = 0,
           rx_length_errors = 0,
           rx_over_errors = 0,
           rx_crc_errors = 0,
           rx_frame_errors = 0,
           rx_fifo_errors = 0,
           rx_missed_errors = 0,
           tx_aborted_errors = 0,
           tx_carrier_errors = 0,
           tx_fifo_errors = 0,
           tx_heartbeat_errors = 0,
           tx_window_errors = 0,
           rx_compressed = 0,
           tx_compressed = 0,
           rx_nohandler = 0,
           rx_otherhost_dropped = 0
       }],
       [{
           nla_len = 100,
           nla_type = IFLA_STATS
       }, {
           rx_packets = 0,
           tx_packets = 0,
           rx_bytes = 0,
           tx_bytes = 0,
           rx_errors = 0,
           tx_errors = 0,
           rx_dropped = 0,
           tx_dropped = 0,
           multicast = 0,
           collisions = 0,
           rx_length_errors = 0,
           rx_over_errors = 0,
           rx_crc_errors = 0,
           rx_frame_errors = 0,
           rx_fifo_errors = 0,
           rx_missed_errors = 0,
           tx_aborted_errors = 0,
           tx_carrier_errors = 0,
           tx_fifo_errors = 0,
           tx_heartbeat_errors = 0,
           tx_window_errors = 0,
           rx_compressed = 0,
           tx_compressed = 0,
           rx_nohandler = 0
       }],
       [{
               nla_len = 12,
               nla_type = IFLA_XDP
           },
           [{
               nla_len = 5,
               nla_type = IFLA_XDP_ATTACHED
           }, XDP_ATTACHED_NONE]
       ],
       [{
               nla_len = 16,
               nla_type = IFLA_LINKINFO
           },
           [{
               nla_len = 10,
               nla_type = IFLA_INFO_KIND
           }, "dummy"]
       ],
       [{
           nla_len = 9,
           nla_type = IFLA_QDISC
       }, "noop"],
       [{
               nla_len = 812,
               nla_type = IFLA_AF_SPEC
           },
           [
               [{
                       nla_len = 140,
                       nla_type = AF_INET
                   },
                   [{
                           nla_len = 136,
                           nla_type = IFLA_INET_CONF
                       },
                       [
                           [IPV4_DEVCONF_FORWARDING - 1] = 1, [IPV4_DEVCONF_MC_FORWARDING - 1] = 0, [IPV4_DEVCONF_PROXY_ARP - 1] = 0, [IPV4_DEVCONF_ACCEPT_REDIRECTS - 1] = 1, [IPV4_DEVCONF_SECURE_REDIRECTS - 1] = 1, [IPV4_DEVCONF_SEND_REDIRECTS - 1] = 1, [IPV4_DEVCONF_SHARED_MEDIA - 1] = 1, [IPV4_DEVCONF_RP_FILTER - 1] = 2, [IPV4_DEVCONF_ACCEPT_SOURCE_ROUTE - 1] = 0, [IPV4_DEVCONF_BOOTP_RELAY - 1] = 0, [IPV4_DEVCONF_LOG_MARTIANS - 1] = 0, [IPV4_DEVCONF_TAG - 1] = 0, [IPV4_DEVCONF_ARPFILTER - 1] = 0, [IPV4_DEVCONF_MEDIUM_ID - 1] = 0, [IPV4_DEVCONF_NOXFRM - 1] = 0, [IPV4_DEVCONF_NOPOLICY - 1] = 0, [IPV4_DEVCONF_FORCE_IGMP_VERSION - 1] = 0, [IPV4_DEVCONF_ARP_ANNOUNCE - 1] = 0, [IPV4_DEVCONF_ARP_IGNORE - 1] = 0, [IPV4_DEVCONF_PROMOTE_SECONDARIES - 1] = 1, [IPV4_DEVCONF_ARP_ACCEPT - 1] = 0, [IPV4_DEVCONF_ARP_NOTIFY - 1] = 0, [IPV4_DEVCONF_ACCEPT_LOCAL - 1] = 0, [IPV4_DEVCONF_SRC_VMARK - 1] = 0, [IPV4_DEVCONF_PROXY_ARP_PVLAN - 1] = 0, [IPV4_DEVCONF_ROUTE_LOCALNET - 1] = 0, [IPV4_DEVCONF_IGMPV2_UNSOLICITED_REPORT_INTERVAL - 1] = 10000, [IPV4_DEVCONF_IGMPV3_UNSOLICITED_REPORT_INTERVAL - 1] = 1000, [IPV4_DEVCONF_IGNORE_ROUTES_WITH_LINKDOWN - 1] = 0, [IPV4_DEVCONF_DROP_UNICAST_IN_L2_MULTICAST - 1] = 0, [IPV4_DEVCONF_DROP_GRATUITOUS_ARP - 1] = 0, [IPV4_DEVCONF_BC_FORWARDING - 1] = 0, ...
                       ]
                   ]
               ],
               [{
                       nla_len = 668,
                       nla_type = AF_INET6
                   },
                   [
                       [{
                           nla_len = 8,
                           nla_type = IFLA_INET6_FLAGS
                       }, 0],
                       [{
                           nla_len = 20,
                           nla_type = IFLA_INET6_CACHEINFO
                       }, {
                           max_reasm_len = 65535,
                           tstamp = 1022034,
                           reachable_time = 34094,
                           retrans_time = 1000
                       }],
                       [{
                               nla_len = 240,
                               nla_type = IFLA_INET6_CONF
                           },
                           [
                               [DEVCONF_FORWARDING] = 0, [DEVCONF_HOPLIMIT] = 64, [DEVCONF_MTU6] = 1500, [DEVCONF_ACCEPT_RA] = 1, [DEVCONF_ACCEPT_REDIRECTS] = 1, [DEVCONF_AUTOCONF] = 1, [DEVCONF_DAD_TRANSMITS] = 1, [DEVCONF_RTR_SOLICITS] = -1, [DEVCONF_RTR_SOLICIT_INTERVAL] = 4000, [DEVCONF_RTR_SOLICIT_DELAY] = 1000, [DEVCONF_USE_TEMPADDR] = 0, [DEVCONF_TEMP_VALID_LFT] = 604800, [DEVCONF_TEMP_PREFERED_LFT] = 86400, [DEVCONF_REGEN_MAX_RETRY] = 3, [DEVCONF_MAX_DESYNC_FACTOR] = 600, [DEVCONF_MAX_ADDRESSES] = 16, [DEVCONF_FORCE_MLD_VERSION] = 0, [DEVCONF_ACCEPT_RA_DEFRTR] = 1, [DEVCONF_ACCEPT_RA_PINFO] = 1, [DEVCONF_ACCEPT_RA_RTR_PREF] = 1, [DEVCONF_RTR_PROBE_INTERVAL] = 60000, [DEVCONF_ACCEPT_RA_RT_INFO_MAX_PLEN] = 0, [DEVCONF_PROXY_NDP] = 0, [DEVCONF_OPTIMISTIC_DAD] = 0, [DEVCONF_ACCEPT_SOURCE_ROUTE] = 0, [DEVCONF_MC_FORWARDING] = 0, [DEVCONF_DISABLE_IPV6] = 0, [DEVCONF_ACCEPT_DAD] = -1, [DEVCONF_FORCE_TLLAO] = 0, [DEVCONF_NDISC_NOTIFY] = 0, [DEVCONF_MLDV1_UNSOLICITED_REPORT_INTERVAL] = 10000, [DEVCONF_MLDV2_UNSOLICITED_REPORT_INTERVAL] = 1000, ...
                           ]
                       ],
                       [{
                               nla_len = 308,
                               nla_type = IFLA_INET6_STATS
                           },
                           [
                               [IPSTATS_MIB_NUM] = 38, [IPSTATS_MIB_INPKTS] = 0, [IPSTATS_MIB_INOCTETS] = 0, [IPSTATS_MIB_INDELIVERS] = 0, [IPSTATS_MIB_OUTFORWDATAGRAMS] = 0, [IPSTATS_MIB_OUTPKTS] = 0, [IPSTATS_MIB_OUTOCTETS] = 0, [IPSTATS_MIB_INHDRERRORS] = 0, [IPSTATS_MIB_INTOOBIGERRORS] = 0, [IPSTATS_MIB_INNOROUTES] = 0, [IPSTATS_MIB_INADDRERRORS] = 0, [IPSTATS_MIB_INUNKNOWNPROTOS] = 0, [IPSTATS_MIB_INTRUNCATEDPKTS] = 0, [IPSTATS_MIB_INDISCARDS] = 0, [IPSTATS_MIB_OUTDISCARDS] = 0, [IPSTATS_MIB_OUTNOROUTES] = 0, [IPSTATS_MIB_REASMTIMEOUT] = 0, [IPSTATS_MIB_REASMREQDS] = 0, [IPSTATS_MIB_REASMOKS] = 0, [IPSTATS_MIB_REASMFAILS] = 0, [IPSTATS_MIB_FRAGOKS] = 0, [IPSTATS_MIB_FRAGFAILS] = 0, [IPSTATS_MIB_FRAGCREATES] = 0, [IPSTATS_MIB_INMCASTPKTS] = 0, [IPSTATS_MIB_OUTMCASTPKTS] = 0, [IPSTATS_MIB_INBCASTPKTS] = 0, [IPSTATS_MIB_OUTBCASTPKTS] = 0, [IPSTATS_MIB_INMCASTOCTETS] = 0, [IPSTATS_MIB_OUTMCASTOCTETS] = 0, [IPSTATS_MIB_INBCASTOCTETS] = 0, [IPSTATS_MIB_OUTBCASTOCTETS] = 0, [IPSTATS_MIB_CSUMERRORS] = 0, ...
                           ]
                       ],
                       [{
                               nla_len = 60,
                               nla_type = IFLA_INET6_ICMP6STATS
                           },
                           [
                               [ICMP6_MIB_NUM] = 7, [ICMP6_MIB_INMSGS] = 0, [ICMP6_MIB_INERRORS] = 0, [ICMP6_MIB_OUTMSGS] = 0, [ICMP6_MIB_OUTERRORS] = 0, [ICMP6_MIB_CSUMERRORS] = 0, [6 /* ICMP6_MIB_??? */ ] = 0
                           ]
                       ],
                       [{
                           nla_len = 20,
                           nla_type = IFLA_INET6_TOKEN
                       }, inet_pton(AF_INET6, "::")],
                       [{
                           nla_len = 5,
                           nla_type = IFLA_INET6_ADDR_GEN_MODE
                       }, IN6_ADDR_GEN_MODE_EUI64]
                   ]
               ]
           ]
       ], ...
   ]
], 32768, MSG_WAITALL, NULL, NULL) = 1452

Это сообщение содержит много информации, но нас сейчас интересует только индекс и название. Индекс интерфейса находится в ifinfomsg, и получить его несложно. Имя интерфейса находится в атрибуте IFLA_IFNAME. Давайте обработаем его.

Значение IFLA_IFNAME можно найти в файле if_link.h. Если вы напишете это слово в вашей IDE, то она  покажет, в каком файле определен макрос с таким же названием. Если вы не можете найти значение, то можете сохранить сообщение в debug файл и посмотреть его там.

Определим функцию handle_rtm_newlink_message следующим образом:

virtual void handle_rtm_newlink_message(ifinfomsg& message_interface_info, char* data_buf, const std::uint32_t& data_len) noexcept override
{
    std::cout << "New interface: " << message_interface_info.ifi_index << std::endl;

    iterate_netlink_message(data_buf, data_len, [&](int attribute_len, int attribute_type, char* attribute_data)
    {
        switch (attribute_type) 
        {
        case IFLA_IFNAME:
            std::cout << "\tname=\"" << attribute_data << "\"" << std::endl;
            break;

        default:
            break;
        }
    });
}

Функция iterate_netlink_message принимает лямбду, которая обработает все атрибуты. Внутри лямбды находится switch по attribute_type, где происходит обработка атрибута IFLA_IFNAME. Внутри кейса IFLA_IFNAME выводится содержимое attribute_data, в котором и находится название интерфейса.

В нашей системе уже существует dummy-интерфейс, так что надо удалить его:

ip link del dev dummy0 type dummy

Теперь собираем и запускаем программу в одном терминале, а затем снова добавляем dummy-интерфейс:

ip link add dev dummy0 type dummy

Посмотрим на вывод программы:

Результат программы совпадает с результатом вызова команды ip a, значит, программа работает правильно.

Отслеживание добавления bridge-интерфейса и обработка его типа

В этом примере мы отследим появления интерфейса и узнаем его тип. Снова перейдем в директорию репозитория и откроем два терминала. В одном запустим контейнер, в другом с помощью скрипта — сетевое пространство этого контейнера. Вне зависимости от более ранней конфигурации запустим программу с помощью strace и выполним:

ip link add dev bridge0 type bridge

В этот раз в файле будет 2 recvfrom, так как rtnetlink для бриджа шлет сообщение RTM_NEWNEIGH, но в рамках текущей задачи оно нас не интересует.

Текст сообщения
recvfrom(3, [{
       nlmsg_len = 1880,
       nlmsg_type = RTM_NEWLINK,
       nlmsg_flags = 0,
       nlmsg_seq = 0,
       nlmsg_pid = 0
   }, {
       ifi_family = AF_UNSPEC,
       ifi_type = ARPHRD_ETHER,
       ifi_index = if_nametoindex("bridge0"),
       ifi_flags = IFF_BROADCAST | IFF_MULTICAST,
       ifi_change = 0xffffffff
   },
   [
       [{
           nla_len = 12,
           nla_type = IFLA_IFNAME
       }, "bridge0"],
       [{
           nla_len = 8,
           nla_type = IFLA_TXQLEN
       }, 1000],
       [{
           nla_len = 5,
           nla_type = IFLA_OPERSTATE
       }, 2],
       [{
           nla_len = 5,
           nla_type = IFLA_LINKMODE
       }, 0],
       [{
           nla_len = 8,
           nla_type = IFLA_MTU
       }, 1500],
       [{
           nla_len = 8,
           nla_type = IFLA_MIN_MTU
       }, 68],
       [{
           nla_len = 8,
           nla_type = IFLA_MAX_MTU
       }, 65535],
       [{
           nla_len = 8,
           nla_type = IFLA_GROUP
       }, 0],
       [{
           nla_len = 8,
           nla_type = IFLA_PROMISCUITY
       }, 0],
       [{
           nla_len = 8,
           nla_type = IFLA_ALLMULTI
       }, 0],
       [{
           nla_len = 8,
           nla_type = IFLA_NUM_TX_QUEUES
       }, 1],
       [{
           nla_len = 8,
           nla_type = IFLA_GSO_MAX_SEGS
       }, 65535],
       [{
           nla_len = 8,
           nla_type = IFLA_GSO_MAX_SIZE
       }, 65536],
       [{
           nla_len = 8,
           nla_type = IFLA_GRO_MAX_SIZE
       }, 65536],
       [{
           nla_len = 8,
           nla_type = IFLA_GSO_IPV4_MAX_SIZE
       }, 65536],
       [{
           nla_len = 8,
           nla_type = IFLA_GRO_IPV4_MAX_SIZE
       }, 65536],
       [{
           nla_len = 8,
           nla_type = IFLA_TSO_MAX_SIZE
       }, 65536],
       [{
           nla_len = 8,
           nla_type = IFLA_TSO_MAX_SEGS
       }, 65535],
       [{
           nla_len = 8,
           nla_type = IFLA_NUM_RX_QUEUES
       }, 1],
       [{
           nla_len = 5,
           nla_type = IFLA_CARRIER
       }, 1],
       [{
           nla_len = 8,
           nla_type = IFLA_CARRIER_CHANGES
       }, 0],
       [{
           nla_len = 8,
           nla_type = IFLA_CARRIER_UP_COUNT
       }, 0],
       [{
           nla_len = 8,
           nla_type = IFLA_CARRIER_DOWN_COUNT
       }, 0],
       [{
           nla_len = 5,
           nla_type = IFLA_PROTO_DOWN
       }, 0],
       [{
           nla_len = 10,
           nla_type = IFLA_ADDRESS
       }, 12: 01: 8 e: 2 b: 2 d: 13],
       [{
           nla_len = 10,
           nla_type = IFLA_BROADCAST
       }, ff: ff: ff: ff: ff: ff],
       [{
           nla_len = 204,
           nla_type = IFLA_STATS64
       }, {
           rx_packets = 0,
           tx_packets = 0,
           rx_bytes = 0,
           tx_bytes = 0,
           rx_errors = 0,
           tx_errors = 0,
           rx_dropped = 0,
           tx_dropped = 0,
           multicast = 0,
           collisions = 0,
           rx_length_errors = 0,
           rx_over_errors = 0,
           rx_crc_errors = 0,
           rx_frame_errors = 0,
           rx_fifo_errors = 0,
           rx_missed_errors = 0,
           tx_aborted_errors = 0,
           tx_carrier_errors = 0,
           tx_fifo_errors = 0,
           tx_heartbeat_errors = 0,
           tx_window_errors = 0,
           rx_compressed = 0,
           tx_compressed = 0,
           rx_nohandler = 0,
           rx_otherhost_dropped = 0
       }],
       [{
           nla_len = 100,
           nla_type = IFLA_STATS
       }, {
           rx_packets = 0,
           tx_packets = 0,
           rx_bytes = 0,
           tx_bytes = 0,
           rx_errors = 0,
           tx_errors = 0,
           rx_dropped = 0,
           tx_dropped = 0,
           multicast = 0,
           collisions = 0,
           rx_length_errors = 0,
           rx_over_errors = 0,
           rx_crc_errors = 0,
           rx_frame_errors = 0,
           rx_fifo_errors = 0,
           rx_missed_errors = 0,
           tx_aborted_errors = 0,
           tx_carrier_errors = 0,
           tx_fifo_errors = 0,
           tx_heartbeat_errors = 0,
           tx_window_errors = 0,
           rx_compressed = 0,
           tx_compressed = 0,
           rx_nohandler = 0
       }],
       [{
               nla_len = 12,
               nla_type = IFLA_XDP
           },
           [{
               nla_len = 5,
               nla_type = IFLA_XDP_ATTACHED
           }, XDP_ATTACHED_NONE]
       ],
       [{
               nla_len = 444,
               nla_type = IFLA_LINKINFO
           },
           [
               [{
                   nla_len = 11,
                   nla_type = IFLA_INFO_KIND
               }, "bridge"],
               [{
                       nla_len = 428,
                       nla_type = IFLA_INFO_DATA
                   },
                   [
                       [{
                           nla_len = 12,
                           nla_type = IFLA_BR_HELLO_TIMER
                       }, 0],
                       [{
                           nla_len = 12,
                           nla_type = IFLA_BR_TCN_TIMER
                       }, 0],
                       [{
                           nla_len = 12,
                           nla_type = IFLA_BR_TOPOLOGY_CHANGE_TIMER
                       }, 0],
                       [{
                           nla_len = 12,
                           nla_type = IFLA_BR_GC_TIMER
                       }, 0],
                       [{
                           nla_len = 8,
                           nla_type = IFLA_BR_FORWARD_DELAY
                       }, 1499 /* 14.99 s */ ],
                       [{
                           nla_len = 8,
                           nla_type = IFLA_BR_HELLO_TIME
                       }, 199 /* 1.99 s */ ],
                       [{
                           nla_len = 8,
                           nla_type = IFLA_BR_MAX_AGE
                       }, 1999 /* 19.99 s */ ],
                       [{
                           nla_len = 8,
                           nla_type = IFLA_BR_AGEING_TIME
                       }, 29999 /* 299.99 s */ ],
                       [{
                           nla_len = 8,
                           nla_type = IFLA_BR_STP_STATE
                       }, 0],
                       [{
                           nla_len = 6,
                           nla_type = IFLA_BR_PRIORITY
                       }, 32768],
                       [{
                           nla_len = 5,
                           nla_type = IFLA_BR_VLAN_FILTERING
                       }, 0],
                       [{
                           nla_len = 6,
                           nla_type = IFLA_BR_GROUP_FWD_MASK
                       }, 0],
                       [{
                           nla_len = 12,
                           nla_type = IFLA_BR_BRIDGE_ID
                       }, {
                           prio = [128, 0],
                           addr = 00: 00: 00: 00: 00: 00
                       }],
                       [{
                           nla_len = 12,
                           nla_type = IFLA_BR_ROOT_ID
                       }, {
                           prio = [128, 0],
                           addr = 00: 00: 00: 00: 00: 00
                       }],
                       [{
                           nla_len = 6,
                           nla_type = IFLA_BR_ROOT_PORT
                       }, 0],
                       [{
                           nla_len = 8,
                           nla_type = IFLA_BR_ROOT_PATH_COST
                       }, 0],
                       [{
                           nla_len = 5,
                           nla_type = IFLA_BR_TOPOLOGY_CHANGE
                       }, 0],
                       [{
                           nla_len = 5,
                           nla_type = IFLA_BR_TOPOLOGY_CHANGE_DETECTED
                       }, 0],
                       [{
                           nla_len = 10,
                           nla_type = IFLA_BR_GROUP_ADDR
                       }, 01: 80: c2: 00: 00: 00],
                       [{
                           nla_len = 12,
                           nla_type = IFLA_BR_MULTI_BOOLOPT
                       }, {
                           optval = 0,
                           optmask = 1 << BR_BOOLOPT_NO_LL_LEARN | 1 << BR_BOOLOPT_MCAST_VLAN_SNOOPING | 1 << BR_BOOLOPT_MST_ENABLE
                       }],
                       [{
                           nla_len = 8,
                           nla_type = 0x30 /* IFLA_BR_??? */
                       }, "\x00\x00\x00\x00"],
                       [{
                           nla_len = 8,
                           nla_type = 0x31 /* IFLA_BR_??? */
                       }, "\x00\x00\x00\x00"],
                       [{
                           nla_len = 6,
                           nla_type = IFLA_BR_VLAN_PROTOCOL
                       }, htons(ETH_P_8021Q)],
                       [{
                           nla_len = 6,
                           nla_type = IFLA_BR_VLAN_DEFAULT_PVID
                       }, 1],
                       [{
                           nla_len = 5,
                           nla_type = IFLA_BR_VLAN_STATS_ENABLED
                       }, 0],
                       [{
                           nla_len = 5,
                           nla_type = IFLA_BR_VLAN_STATS_PER_PORT
                       }, 0],
                       [{
                           nla_len = 5,
                           nla_type = IFLA_BR_MCAST_ROUTER
                       }, 1],
                       [{
                           nla_len = 5,
                           nla_type = IFLA_BR_MCAST_SNOOPING
                       }, 1],
                       [{
                           nla_len = 5,
                           nla_type = IFLA_BR_MCAST_QUERY_USE_IFADDR
                       }, 0],
                       [{
                           nla_len = 5,
                           nla_type = IFLA_BR_MCAST_QUERIER
                       }, 0],
                       [{
                           nla_len = 5,
                           nla_type = IFLA_BR_MCAST_STATS_ENABLED
                       }, 0],
                       [{
                           nla_len = 8,
                           nla_type = IFLA_BR_MCAST_HASH_ELASTICITY
                       }, 16], ...
                   ]
               ]
           ]
       ],
       [{
           nla_len = 9,
           nla_type = IFLA_QDISC
       }, "noop"],
       [{
               nla_len = 812,
               nla_type = IFLA_AF_SPEC
           },
           [
               [{
                       nla_len = 140,
                       nla_type = AF_INET
                   },
                   [{
                           nla_len = 136,
                           nla_type = IFLA_INET_CONF
                       },
                       [
                           [IPV4_DEVCONF_FORWARDING - 1] = 1, [IPV4_DEVCONF_MC_FORWARDING - 1] = 0, [IPV4_DEVCONF_PROXY_ARP - 1] = 0, [IPV4_DEVCONF_ACCEPT_REDIRECTS - 1] = 1, [IPV4_DEVCONF_SECURE_REDIRECTS - 1] = 1, [IPV4_DEVCONF_SEND_REDIRECTS - 1] = 1, [IPV4_DEVCONF_SHARED_MEDIA - 1] = 1, [IPV4_DEVCONF_RP_FILTER - 1] = 2, [IPV4_DEVCONF_ACCEPT_SOURCE_ROUTE - 1] = 0, [IPV4_DEVCONF_BOOTP_RELAY - 1] = 0, [IPV4_DEVCONF_LOG_MARTIANS - 1] = 0, [IPV4_DEVCONF_TAG - 1] = 0, [IPV4_DEVCONF_ARPFILTER - 1] = 0, [IPV4_DEVCONF_MEDIUM_ID - 1] = 0, [IPV4_DEVCONF_NOXFRM - 1] = 0, [IPV4_DEVCONF_NOPOLICY - 1] = 0, [IPV4_DEVCONF_FORCE_IGMP_VERSION - 1] = 0, [IPV4_DEVCONF_ARP_ANNOUNCE - 1] = 0, [IPV4_DEVCONF_ARP_IGNORE - 1] = 0, [IPV4_DEVCONF_PROMOTE_SECONDARIES - 1] = 1, [IPV4_DEVCONF_ARP_ACCEPT - 1] = 0, [IPV4_DEVCONF_ARP_NOTIFY - 1] = 0, [IPV4_DEVCONF_ACCEPT_LOCAL - 1] = 0, [IPV4_DEVCONF_SRC_VMARK - 1] = 0, [IPV4_DEVCONF_PROXY_ARP_PVLAN - 1] = 0, [IPV4_DEVCONF_ROUTE_LOCALNET - 1] = 0, [IPV4_DEVCONF_IGMPV2_UNSOLICITED_REPORT_INTERVAL - 1] = 10000, [IPV4_DEVCONF_IGMPV3_UNSOLICITED_REPORT_INTERVAL - 1] = 1000, [IPV4_DEVCONF_IGNORE_ROUTES_WITH_LINKDOWN - 1] = 0, [IPV4_DEVCONF_DROP_UNICAST_IN_L2_MULTICAST - 1] = 0, [IPV4_DEVCONF_DROP_GRATUITOUS_ARP - 1] = 0, [IPV4_DEVCONF_BC_FORWARDING - 1] = 0, ...
                       ]
                   ]
               ],
               [{
                       nla_len = 668,
                       nla_type = AF_INET6
                   },
                   [
                       [{
                           nla_len = 8,
                           nla_type = IFLA_INET6_FLAGS
                       }, 0],
                       [{
                           nla_len = 20,
                           nla_type = IFLA_INET6_CACHEINFO
                       }, {
                           max_reasm_len = 65535,
                           tstamp = 1180525,
                           reachable_time = 21857,
                           retrans_time = 1000
                       }],
                       [{
                               nla_len = 240,
                               nla_type = IFLA_INET6_CONF
                           },
                           [
                               [DEVCONF_FORWARDING] = 0, [DEVCONF_HOPLIMIT] = 64, [DEVCONF_MTU6] = 1500, [DEVCONF_ACCEPT_RA] = 1, [DEVCONF_ACCEPT_REDIRECTS] = 1, [DEVCONF_AUTOCONF] = 1, [DEVCONF_DAD_TRANSMITS] = 1, [DEVCONF_RTR_SOLICITS] = -1, [DEVCONF_RTR_SOLICIT_INTERVAL] = 4000, [DEVCONF_RTR_SOLICIT_DELAY] = 1000, [DEVCONF_USE_TEMPADDR] = 0, [DEVCONF_TEMP_VALID_LFT] = 604800, [DEVCONF_TEMP_PREFERED_LFT] = 86400, [DEVCONF_REGEN_MAX_RETRY] = 3, [DEVCONF_MAX_DESYNC_FACTOR] = 600, [DEVCONF_MAX_ADDRESSES] = 16, [DEVCONF_FORCE_MLD_VERSION] = 0, [DEVCONF_ACCEPT_RA_DEFRTR] = 1, [DEVCONF_ACCEPT_RA_PINFO] = 1, [DEVCONF_ACCEPT_RA_RTR_PREF] = 1, [DEVCONF_RTR_PROBE_INTERVAL] = 60000, [DEVCONF_ACCEPT_RA_RT_INFO_MAX_PLEN] = 0, [DEVCONF_PROXY_NDP] = 0, [DEVCONF_OPTIMISTIC_DAD] = 0, [DEVCONF_ACCEPT_SOURCE_ROUTE] = 0, [DEVCONF_MC_FORWARDING] = 0, [DEVCONF_DISABLE_IPV6] = 0, [DEVCONF_ACCEPT_DAD] = 1, [DEVCONF_FORCE_TLLAO] = 0, [DEVCONF_NDISC_NOTIFY] = 0, [DEVCONF_MLDV1_UNSOLICITED_REPORT_INTERVAL] = 10000, [DEVCONF_MLDV2_UNSOLICITED_REPORT_INTERVAL] = 1000, ...
                           ]
                       ],
                       [{
                               nla_len = 308,
                               nla_type = IFLA_INET6_STATS
                           },
                           [
                               [IPSTATS_MIB_NUM] = 38, [IPSTATS_MIB_INPKTS] = 0, [IPSTATS_MIB_INOCTETS] = 0, [IPSTATS_MIB_INDELIVERS] = 0, [IPSTATS_MIB_OUTFORWDATAGRAMS] = 0, [IPSTATS_MIB_OUTPKTS] = 0, [IPSTATS_MIB_OUTOCTETS] = 0, [IPSTATS_MIB_INHDRERRORS] = 0, [IPSTATS_MIB_INTOOBIGERRORS] = 0, [IPSTATS_MIB_INNOROUTES] = 0, [IPSTATS_MIB_INADDRERRORS] = 0, [IPSTATS_MIB_INUNKNOWNPROTOS] = 0, [IPSTATS_MIB_INTRUNCATEDPKTS] = 0, [IPSTATS_MIB_INDISCARDS] = 0, [IPSTATS_MIB_OUTDISCARDS] = 0, [IPSTATS_MIB_OUTNOROUTES] = 0, [IPSTATS_MIB_REASMTIMEOUT] = 0, [IPSTATS_MIB_REASMREQDS] = 0, [IPSTATS_MIB_REASMOKS] = 0, [IPSTATS_MIB_REASMFAILS] = 0, [IPSTATS_MIB_FRAGOKS] = 0, [IPSTATS_MIB_FRAGFAILS] = 0, [IPSTATS_MIB_FRAGCREATES] = 0, [IPSTATS_MIB_INMCASTPKTS] = 0, [IPSTATS_MIB_OUTMCASTPKTS] = 0, [IPSTATS_MIB_INBCASTPKTS] = 0, [IPSTATS_MIB_OUTBCASTPKTS] = 0, [IPSTATS_MIB_INMCASTOCTETS] = 0, [IPSTATS_MIB_OUTMCASTOCTETS] = 0, [IPSTATS_MIB_INBCASTOCTETS] = 0, [IPSTATS_MIB_OUTBCASTOCTETS] = 0, [IPSTATS_MIB_CSUMERRORS] = 0, ...
                           ]
                       ],
                       [{
                               nla_len = 60,
                               nla_type = IFLA_INET6_ICMP6STATS
                           },
                           [
                               [ICMP6_MIB_NUM] = 7, [ICMP6_MIB_INMSGS] = 0, [ICMP6_MIB_INERRORS] = 0, [ICMP6_MIB_OUTMSGS] = 0, [ICMP6_MIB_OUTERRORS] = 0, [ICMP6_MIB_CSUMERRORS] = 0, [6 /* ICMP6_MIB_??? */ ] = 0
                           ]
                       ],
                       [{
                           nla_len = 20,
                           nla_type = IFLA_INET6_TOKEN
                       }, inet_pton(AF_INET6, "::")],
                       [{
                           nla_len = 5,
                           nla_type = IFLA_INET6_ADDR_GEN_MODE
                       }, IN6_ADDR_GEN_MODE_EUI64]
                   ]
               ]
           ]
       ], ...
   ]
], 32768, MSG_WAITALL, NULL, NULL) = 1880

Данные о типе интерфейса хранятся в атрибуте IFLA_INFO_KIND, что находится внутри атрибута IFLA_LINKINFO. Этот пример был создан как раз для демонстрации вложенных атрибутов и того, как их надо обрабатывать. Атрибут не имеет внутри себя флага nested, но бывают и такие, что имеют. Для отработки вложенных интерфейсов можно использовать функцию iterate_netlink_message так же, как и для невложенных атрибутов.

Определим функцию handle_rtm_newlink_message следующим образом:

virtual void handle_rtm_newlink_message(ifinfomsg& message_interface_info, char* data_buf, const std::uint32_t& data_len) noexcept override
{
    std::cout << "New interface: " << message_interface_info.ifi_index << std::endl;

    iterate_netlink_message(data_buf, data_len, [&](int attribute_len, int attribute_type, char* attribute_data)
    {
        switch (attribute_type) 
        {
        case IFLA_IFNAME:
            std::cout << "\tname=\"" << attribute_data << "\"" << std::endl;
            break;

        case IFLA_LINKINFO:
            iterate_netlink_message(attribute_data, attribute_len, [&](int attribute_len2, int attribute_type2, char* attribute_data2)
            {
                switch (attribute_type2)
                {
                case IFLA_INFO_KIND:
                    std::cout << "\ttype=\"" << attribute_data2 << "\"" << std::endl;
                    break;

                default:
                    break;
                };
            });
            break;

        default:
            break;
        }
    });
}

Так как в системе уже существует bridge-интерфейс, надо удалить его:

ip link del dev bridge0 type bridge

Теперь соберем и запустим программу в одном терминале, а затем снова добавим bridge-интерфейс:

ip link add dev bridge0 type bridge

Посмотрим на вывод программы:

Отслеживание принадлежности интерфейса vlan’ам

Напомню команды для конфигурации, если вы пропустили предыдущие пункты:

ip link add dev dummy0 type dummy
ip link add dev bridge0 type bridge

Подключаем dummy0 к бриджу:

ip link set dev dummy0 master bridge0

Добавляем интерфейс в диапазон vlan’ов:

bridge vlan add vid 10-20 dev dummy0

Запускаем программу с strace и добавляем dummy интерфейс во vlan’ы: 

bridge vlan add vid 25 dev dummy0

Для тестов может потребоваться удаление vlan’ов, вот нужные команды:

bridge vlan del vid 10-20 dev dummy0
bridge vlan del vid 25 dev dummy0

Так как в одном сообщении приходит вся информация о vlan’е, то можно анализировать одно последнее сообщение.

Текст сообщения
recvfrom(3, [{
       nlmsg_len = 452,
       nlmsg_type = RTM_NEWLINK,
       nlmsg_flags = 0,
       nlmsg_seq = 0,
       nlmsg_pid = 0
   }, {
       ifi_family = AF_BRIDGE,
       ifi_type = ARPHRD_ETHER,
       ifi_index = if_nametoindex("dummy0"),
       ifi_flags = IFF_BROADCAST | IFF_NOARP,
       ifi_change = 0
   },
   [
       [{
           nla_len = 11,
           nla_type = IFLA_IFNAME
       }, "dummy0"],
       [{
           nla_len = 8,
           nla_type = IFLA_MASTER
       }, 3],
       [{
           nla_len = 8,
           nla_type = IFLA_MTU
       }, 1500],
       [{
           nla_len = 5,
           nla_type = IFLA_OPERSTATE
       }, 2],
       [{
           nla_len = 10,
           nla_type = IFLA_ADDRESS
       }, 52],
       [{
               nla_len = 336,
               nla_type = NLA_F_NESTED | IFLA_PROTINFO
           },
           [
               [{
                   nla_len = 5,
                   nla_type = IFLA_BRPORT_STATE
               }, 0],
               [{
                   nla_len = 6,
                   nla_type = IFLA_BRPORT_PRIORITY
               }, 32],
               [{
                   nla_len = 8,
                   nla_type = IFLA_BRPORT_COST
               }, 100],
               [{
                   nla_len = 5,
                   nla_type = IFLA_BRPORT_MODE
               }, 0],
               [{
                   nla_len = 5,
                   nla_type = IFLA_BRPORT_GUARD
               }, 0],
               [{
                   nla_len = 5,
                   nla_type = IFLA_BRPORT_PROTECT
               }, 0],
               [{
                   nla_len = 5,
                   nla_type = IFLA_BRPORT_FAST_LEAVE
               }, 0],
               [{
                   nla_len = 5,
                   nla_type = IFLA_BRPORT_MCAST_TO_UCAST
               }, 0],
               [{
                   nla_len = 5,
                   nla_type = IFLA_BRPORT_LEARNING
               }, 1],
               [{
                   nla_len = 5,
                   nla_type = IFLA_BRPORT_UNICAST_FLOOD
               }, 1],
               [{
                   nla_len = 5,
                   nla_type = IFLA_BRPORT_MCAST_FLOOD
               }, 1],
               [{
                   nla_len = 5,
                   nla_type = IFLA_BRPORT_BCAST_FLOOD
               }, 1],
               [{
                   nla_len = 5,
                   nla_type = IFLA_BRPORT_PROXYARP
               }, 0],
               [{
                   nla_len = 5,
                   nla_type = IFLA_BRPORT_PROXYARP_WIFI
               }, 0],
               [{
                   nla_len = 12,
                   nla_type = IFLA_BRPORT_ROOT_ID
               }, {
                   prio = [128, 0],
                   addr = 52: 42: e1: 72: 4 f: 64
               }],
               [{
                   nla_len = 12,
                   nla_type = IFLA_BRPORT_BRIDGE_ID
               }, {
                   prio = [128, 0],
                   addr = 52: 42: e1: 72: 4 f: 64
               }],
               [{
                   nla_len = 6,
                   nla_type = IFLA_BRPORT_DESIGNATED_PORT
               }, 32769],
               [{
                   nla_len = 6,
                   nla_type = IFLA_BRPORT_DESIGNATED_COST
               }, 0],
               [{
                   nla_len = 6,
                   nla_type = IFLA_BRPORT_ID
               }, 32769],
               [{
                   nla_len = 6,
                   nla_type = IFLA_BRPORT_NO
               }, 1],
               [{
                   nla_len = 5,
                   nla_type = IFLA_BRPORT_TOPOLOGY_CHANGE_ACK
               }, 0],
               [{
                   nla_len = 5,
                   nla_type = IFLA_BRPORT_CONFIG_PENDING
               }, 0],
               [{
                   nla_len = 5,
                   nla_type = IFLA_BRPORT_VLAN_TUNNEL
               }, 0],
               [{
                   nla_len = 6,
                   nla_type = IFLA_BRPORT_GROUP_FWD_MASK
               }, 0],
               [{
                   nla_len = 5,
                   nla_type = IFLA_BRPORT_NEIGH_SUPPRESS
               }, 0],
               [{
                   nla_len = 5,
                   nla_type = IFLA_BRPORT_MRP_RING_OPEN
               }, 0],
               [{
                   nla_len = 5,
                   nla_type = IFLA_BRPORT_MRP_IN_OPEN
               }, 0],
               [{
                   nla_len = 5,
                   nla_type = IFLA_BRPORT_ISOLATED
               }, 0],
               [{
                   nla_len = 5,
                   nla_type = IFLA_BRPORT_LOCKED
               }, 0],
               [{
                   nla_len = 5,
                   nla_type = IFLA_BRPORT_MAB
               }, 0],
               [{
                   nla_len = 5,
                   nla_type = IFLA_BRPORT_NEIGH_VLAN_SUPPRESS
               }, 0],
               [{
                   nla_len = 12,
                   nla_type = IFLA_BRPORT_MESSAGE_AGE_TIMER
               }, 0], ...
           ]
       ],
       [{
               nla_len = 36,
               nla_type = IFLA_AF_SPEC
           },
           [
               [{
                   nla_len = 8,
                   nla_type = IFLA_BRIDGE_VLAN_INFO
               }, {
                   flags = BRIDGE_VLAN_INFO_PVID | BRIDGE_VLAN_INFO_UNTAGGED,
                   vid = 1
               }],
               [{
                   nla_len = 8,
                   nla_type = IFLA_BRIDGE_VLAN_INFO
               }, {
                   flags = BRIDGE_VLAN_INFO_RANGE_BEGIN,
                   vid = 10
               }],
               [{
                   nla_len = 8,
                   nla_type = IFLA_BRIDGE_VLAN_INFO
               }, {
                   flags = BRIDGE_VLAN_INFO_RANGE_END,
                   vid = 20
               }],
               [{
                   nla_len = 8,
                   nla_type = IFLA_BRIDGE_VLAN_INFO
               }, {
                   flags = 0,
                   vid = 25
               }]
           ]
       ]
   ]
], 32768, MSG_WAITALL, NULL, NULL) = 452

Далее нужно обратить внимание на самый конец, а именно на аргументы с типом IFLA_BRIDGE_VLAN_INFO. Важно учесть, что vlan’ы могут передаваться в виде диапазона. Можно заметить, что в теле атрибута есть некий флаг и vid. Информация об этих данных находится в файле if_bridge.h:

Flags и vid имеют длину два байта. Эту же информацию можно получить из debug-файла, если в функции handle_rtm_newlink установить переменную is_debug_message на true. Проанализируем и сравним trace с бинарным файлом:

В этой части сообщения все элементы двухбайтные, то есть все параметры занимают два байта и 00 — это часть параметра.

  • 24 в hex — это 36 в десятичной системе счисления, nla_len.

  • 1A — это IFLA_AF_SPEC, nla_type.

  • 08 — длина атрибута nla_len.

  • 02 — IFLA_BRIDGE_VLAN_INFO, nla_type.

  • 06 — BRIDGE_VLAN_INFO_PVID | BRIDGE_VLAN_INFO_UNTAGGED, flags.

  • 01 — это значение vid - vid.

  • 08 — длина атрибута nla_len.

  • 02 — IFLA_BRIDGE_VLAN_INFO, nla_type.

  • 08 — BRIDGE_VLAN_INFO_RANGE_BEGIN, то есть начало диапазона vlan’ов, flags.

  • 0A в hex — это 10 в десятичной системе счисления, vid.

  • 08 — длина атрибута, nla_len.

  • 02 — IFLA_BRIDGE_VLAN_INFO, nla_type.

  • 10 — BRIDGE_VLAN_INFO_RANGE_END, то есть конец диапазона vlan’ов, flags.

  • 14 в hex — это 20 в десятичной системе счисления, vid.

  • 08 — длина атрибута, nla_len.

  • 02 — IFLA_BRIDGE_VLAN_INFO, nla_type.

  • 00 — значение флага, то есть просто vlan flags.

  • 19 в hex — это 25 в десятичной системе счисления, vid.

Определим функцию handle_rtm_newlink_message:

virtual void handle_rtm_newlink_message(ifinfomsg& message_interface_info, char* data_buf, const std::uint32_t& data_len) noexcept override
{
	std::cout << "New interface: " << message_interface_info.ifi_index << std::endl;

	iterate_netlink_message(data_buf, data_len, [&](int attribute_len, int attribute_type, char* attribute_data)
	{
		switch (attribute_type) 
		{
		case IFLA_IFNAME:
			std::cout << "\tname=\"" << attribute_data << "\"" << std::endl;
			break;

		case IFLA_LINKINFO:
			iterate_netlink_message(attribute_data, attribute_len, [&](int attribute_len2, int attribute_type2, char* attribute_data2)
			{
				switch (attribute_type2)
				{
				case IFLA_INFO_KIND:
					std::cout << "\ttype=\"" << attribute_data2 << "\"" << std::endl;
					break;

				default:
					break;
				};
			});
			break;

		case IFLA_AF_SPEC:
			std::cout << "\tVlans: ";

			iterate_netlink_message(attribute_data, attribute_len, [&](int attribute_len2, int attribute_type2, char* attribute_data2)
			{
				if (attribute_type2 == IFLA_BRIDGE_VLAN_INFO)
				{
					// In reality, it makes no sense to make it static, it is static only for demonstration purposes.
					static int vlan_range_begin = 0;

					std::uint16_t flag;
					set_int_from_chars(flag, attribute_data2);
					std::uint16_t vlan_id;
					set_int_from_chars(vlan_id, &attribute_data2[2]);

					if (flag & BRIDGE_VLAN_INFO_RANGE_BEGIN)
					{
						vlan_range_begin = vlan_id;
					}
					else if (flag & BRIDGE_VLAN_INFO_RANGE_END)
					{
						for (int i = vlan_range_begin; i <= vlan_id; ++i)
							std::cout << i << std::endl;
					}
					else
					{
						std::cout << vlan_id << std::endl;
					}
				}
			});
			break;

		default:
			break;
		}
	});
}

Посмотрим на вывод программы:

Результат совпадает с реальной конфигурацией системы. Таким образом в Linux мы можем обрабатывать изменения в конфигурации сетевых интерфейсов.

Отправка запросов

Если запустить программу спустя некоторое время работы системы, потребуется получить состояние интерфейсов на момент запуска вашей программы. Для этого надо в функции pre_epoll отправить запрос в netlink-сокет с помощью функции send. Дескриптор netlink-сокета хранится в переменной netlink_socket.

Для получения данных воспользуемся strace:

strace -o trace ip a

Находим в получившемся файле вызов функции sendto с nlmsg_type, равным RTM_GETLINK, и копируем ее вызов в файл для анализа:

sendto(3, [{
		nlmsg_len = 40,
		nlmsg_type = RTM_GETLINK,
		nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP,
		nlmsg_seq = 1735024147,
		nlmsg_pid = 0
	}, {
		ifi_family = AF_UNSPEC,
		ifi_type = ARPHRD_NETROM,
		ifi_index = 0,
		ifi_flags = 0,
		ifi_change = 0
	},
	[{
		nla_len = 8,
		nla_type = IFLA_EXT_MASK
	}, RTEXT_FILTER_VF | RTEXT_FILTER_SKIP_STATS]
], 40, 0, NULL, 0) = 40

Воссоздать его по sendto проблематично, так что, возможно, все-таки придется посмотреть исходники ip_route2. Но я предоставлю готовую функцию, которая делает запрос данных:

void request_interfaces() noexcept
{
	// Request struct
	struct 
	{
		nlmsghdr nlh;
		ifinfomsg ifm;
		rtattr ext_req __attribute__((aligned(NLMSG_ALIGNTO)));
		unsigned int ext_filter_mask;
	} request;

	// Fill request struct    
	memset(&request, 0, sizeof(request));
	request.nlh.nlmsg_len = sizeof(request);
	request.nlh.nlmsg_type = RTM_GETLINK;
	request.nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST;
	request.ifm.ifi_family = AF_PACKET;
	request.ext_req.rta_type = IFLA_EXT_MASK;
	request.ext_req.rta_len = RTA_LENGTH(sizeof(unsigned int));
	request.ext_filter_mask = RTEXT_FILTER_VF | RTEXT_FILTER_SKIP_STATS;

	// Request all interfaces info without vlan
	send(netlink_socket, &request, sizeof(request), 0);
}

Если после такого запроса вы не получили нужной вам информации, то можете посмотреть на возможные фильтры для параметра ext_filter_mask в файле rtnetlink.h

Добавим функцию в класс и вызовем внутри метода pre_epoll. После этого при запуске программы увидим всю изначальную конфигурацию сетевых интерфейсов.

Подведу итоги. В этой статье я рассказал, как в Linux можно обрабатывать конфигурацию сетевых интерфейсов. Я постарался сделать статью простой для понимания, чтобы человек с любой квалификацией мог взять и написать программу. Материал может быть полезен и просто любителям Linux, так как он раскрывает некоторые подробности его работы. На всякий случай продублирую ссылку на свой проект на Gitflic.

Комментарии (0)