Последние записи
- TChromium (CEF3), сохранение изображений
- Как в Delphi XE обнулить таймер?
- Изменить цвет шрифта TextBox на форме
- Ресайз PNG без потери прозрачности
- Вывод на печать графического файла
- Взаимодействие через командную строку
- Перенести программу из Delphi в Lazarus
- Определить текущую ОС
- Автоматическая смена языка (раскладки клавиатуры)
- Сравнение языков на массивах. Часть 2
Интенсив по Python: Работа с API и фреймворками 24-26 ИЮНЯ 2022. Знаете Python, но хотите расширить свои навыки?
Slurm подготовили для вас особенный продукт! Оставить заявку по ссылке - https://slurm.club/3MeqNEk
Online-курс Java с оплатой после трудоустройства. Каждый выпускник получает предложение о работе
И зарплату на 30% выше ожидаемой, подробнее на сайте академии, ссылка - ttps://clck.ru/fCrQw
23rd
Фев
Работа с NetLink в Linux. Часть 1
Posted by Chas under Журнал, Статьи
И вновь, после вынужденного долгого перерыва приветствую уважаемых читателей нашего журнала. За время этого перерыва накопилось достаточно много интересных материалов, которые я постараюсь осветить в новых статьях. Начну с этой. Надеюсь, что эта информация кому-то поможет и пригодится, как, в свое время, помогла мне. В прошлой статье я рассказал о системном вызове IOCTL и обещал рассказать о Netlink. Пришло время выполнять обещание 🙂 К сожалению, невозможно изложить все в рамках одной журнальной статьи. Поэтому материал я разбил на несколько частей. Настоящая статья – первая часть.
Олег Кутков
by Oleg Kutkov elenbert@gmail.com
Итак, Netlink представляет из себя особый компонент Linux ядра. С ним можно общаться через обычный сокет передавая и принимая сообщения, сформированные особым образом. Что же дает нам netlink?
С помощью Netlink мы можем:
- получать уведомления об изменении сетевых интерфейсов (название изменившегося интерфейса и что именно произошло), таблиц маршрутизации, файрволла;
- управлять всеми таблицами маршрутизации;
- управлять параметрами сетевых интерфейсов;
- управлять параметрами файрволла;
- управлять arp таблицей;
- реализовать взаимодействие со своим модулем в ядре.
Кроме этого Netlink позволяет нам отказаться от устаревшего вызова ioctl, упростить и унифицировать код, так как все операции выполняются посредством стандартного сокета. Гарантируется быстрая работа кода и его общая надежность. Именно Netlink использует мощная утилита администрирования iproute2, пришедшая на смену утилитам ifconfig, route и другие.
К сожалению о Netlink очень мало материалов как на русском языке, так и на английском. Пожалуй, наиболее ценным источником можно считать две статьи в Linux journal и Doxygen- документацию. В этой статье я постараюсь приподнять завесу тайны и рассказать как можно больше.
Архитектура Netlink
Как уже говорилось выше, Netlink – это особый компонент ядра, с которым пользователь может общаться посредством обычных сокетов. Для работы с netlink существует особое семейство протоколов – AF_NETLINK, его необходимо указывать при создании нового сокета. Тип сокета следует выбирать SOCK_RAW или SOCK_DGRAM, для протокола, в данном случае, нет разницы. Как можно догадаться, протокол netlinka является диаграммным и не гарантирует доставку сообщений, хоть и старается это сделать всеми доступными средствами.
Каждое сообщение netlink представляет собой поток байт, содержащий один или несколько заголовков, представленных структурой nlmsghdr, а так же связанных с ними данными, которые называются «полезной нагрузкой» (playload). Сообщение, во время доставки, может быть разбито на несколько частей. В таких случаях каждый следующий пакет помечается флагом NLM_F_MULTI, а последний флагом NLMSG_DONE. Для разбора сообщений имеется целый набор макросов, определенный в заголовочном файле netlink.h. Там же определено все прочее, связанное с Netlink.
Надо сказать, что существует отдельная библиотека для работы с netlink – libnl. Она реализует особый уровень абстракции над netlink сокетами и предоставляет множество методов. Лично мне она не очень нравится, т.к. немного имеет немного запутанный и плохо документированный API, который любит часто меняться, что требует изменений и в приложениях, использующих эту библиотеку. Я один раз напоролся на такой сюрприз, поэтому мы не будет рассматривать эту библиотеку, а реализуем весь протокол сами, увидите, это не очень сложно.
Создание сокета netlink
Объявление netlink сокета выглядит так:
Где:
AF_NETLINK – протокол Netlink,
SOCK_RAW – тип сокета,
NETLINK_ROUTE – семейство Netlink протокола
Последний параметр может быть различным, в зависимости от того, что мы именно хотим получить от Netlink.
Приведу таблицу со наиболее интересными параметрами (полный список параметров можно посмотреть в документации):
- NETLINK_ROUTE – получать уведомления об изменениях таблицы маршрутизации и сетевых интерфейсов (так же может использоваться для изменения всех параметров вышеперечисленных объектов).
- NETLINK_USERSOCK – зарезервировано для определения пользовательских протоколов.
- NETLINK_FIREWALL – служит для передачи IPv4 пакетов из сетевого фильтра на пользовательский уровень
- NETLINK_INET_DIAG – мониторинг inet сокетов
- NETLINK_NFLOG – ULOG сетевого/пакетного фильтра
- NETLINK_SELINUX – получать уведомления от системы Selinux
- NETLINK_NETFILTER – работа с подсистемой сетевого фильтра
- NETLINK_KOBJECT_UEVENT – получение сообщений ядра
Далее созданные сокет можно использовать для отправки сообщений, с помощью стандартной функции send и приема сообщений с помощью recvmsg.
Сообщения Netlink
Как уже выше сообщалось – каждое netlink сообщение представлено одним или несколькими заголовками, за которыми следуют полезные данные. Заголовок сообщения представлен структурой nlmsghdr:
{
__u32 nlmsg_len; // размер сообщения, с учетом заголовка
__u16 nlmsg_type; // тип содержимого сообщения (об этом ниже)
__u16 nlmsg_flags; // различные флаги сообщения
__u32 nlmsg_seq; // порядковый номер сообщения
__u32 nlmsg_pid; // идентификатор процесса (PID), отославшего сообщение
};Syhi-подсветка кода
Поле nlmsg_type может указывать на один из стандартных типов сообщений:
NLMSG_NOOP – сообщения такого типа игнорируются.
NLMSG_ERROR – сообщение с ошибкой, и в секции полезных данных будет структура nlmsgerr (о ней чуть ниже)
NLMSG_DONE – сообщение с этим флагом должно завершать сообщение, разбитое на несколько частей
Структура nlmsgerr:
{
int error; // отрицательное значение кода ошибки
struct nlmsghdr msg; // заголовок сообщения, связанного с ошибкой
};Syhi-подсветка кода
Сообщения могут быть одного или нескольких (различные типы объеденяются с помощью операции логического или – |) типов:
- NLM_F_REQUEST – сообщение – запрос чего либо
- NLM_F_MULTI – сообщение, часть сообщения разбитого на части
- NLM_F_ACK – сообщение – запрос подтверждения
- NLM_F_ECHO – эхо запрос. обычное направление – запросы из уровня ядра на пользовательский уровень
- NLM_F_ROOT – данный тип запроса возвращает некую таблицу, внутри некой сущности
- NLM_F_MATCH – запрос возвращает все найденные соответствия
- NLM_F_ATOMIC – возвращает атомарный срез некой таблицы
- NLM_F_DUMP – аналог NLM_F_ROOT|NLM_F_MATCH
Дополнительные флаги:
- NLM_F_REPLACE – заменить существующий аналогичный объект
- NLM_F_EXCL – не заменять, если такой объект уже существует
- NLM_F_CREATE – создать объект, если он не существует
- NLM_F_APPEND – добавить объект в список к уже существующему
Для идентификации клиентов (на уровне ядра и на пользовательском уровне) существует специальная адресная структура – nladdr:
{
sa_family_t nl_family; // семейство протоколов – всегда AF_NETLINK
unsigned short nl_pad; // поле всегда заполнено нулями
pid_t nl_pid; // идентификатор процесса
__u32 nl_groups; // маска групп получателей/отправителей
};Syhi-подсветка кода
nl_pid – это уникальный адрес сокета. Для клиентов в ядре он всегда равен нулю. Для клиентов на пользовательском уровне он равен идентификатору процесса, владеющего сокетом. Каждый идентификатор должен быть уникальным, поэтому тут вы можете натолкнутся на проблему, когда попытаетесь создать несколько netlink сокетов в многопоточном приложении: при создании нового сокета будет возвращаться ошибка «Operation not permitted».
Для обхода данного ограничения следуют nl_pid присваивать значение данного выражения:
Присваивать значение идентификатора следует до того, как будет вызван bind() для сокета.
Так же идентификатору можно присвоить нулевое значение. В этом случае генерацией уникальных идентификаторов будет заниматься ядро, но первому сокету созданному в приложение всегда будет присваиваться значение идентификатора данного приложения.
nl_groups – это битовая маска, каждый бит которой представляет номер группы netlink. При вызове bind() для сокета netlink следует указывать битовую маску группы, которую желает прослушивать приложение, в данном контексте. Различные группы могут быть объединены с помощью логического или – |
Основные группы определены в заголовочном файле netlink. Пример некоторых из них:
- RTMGRP_LINK – эта группа получает уведомления об изменениях в сетевых интерфейсах (интерфейс удалился, добавился, опустился, поднялся)
- RTMGRP_IPV4_IFADDR – эта группа получает уведомления об изменениях в IPv4 адресах интерфейсов (адрес был добавлен или удален)
- RTMGRP_IPV6_IFADDR – эта группа получает уведомления об изменениях в IPv6 адресах интерфейсов (адрес был добавлен или удален)
- RTMGRP_IPV4_ROUTE – эта группа получает уведомления об изменениях в таблице маршрутизации для IPv4 адресов
- RTMGRP_IPV6_ROUTE – эта группа получает уведомления об изменениях в таблице маршрутизации для IPv6 адресов
После структуры заголовка nlmsghdr всегда расположен указатель на блок данных. Доступ к нему можно получить с помощью макросов, о которых будет рассказано далее.
Макросы Netlink
- NLMSG_ALIGN – округляет размер сообщения netlink до ближайшего большего значения, выровненного по границе.
- NLMSG_LENGTH – принимает в качестве параметра размер поля данных (payload) и возвращает выровненное по границе значение размера для записи в поле nlmsg_len заголовка nlmsghdr.
- NLMSG_SPACE – возвращает размер, который займут данные указанной длины в пакете netlink.
- NLMSG_DATA – возвращает указатель на данные, связанные с переданным заголовком nlmsghdr.
- NLMSG_NEXT – возвращает следующую часть сообщения, состоящего из множества частей. Макрос принимает следующий заголовок nlmsghdr в сообщении, состоящем из множества частей. Вызывающее приложение должно проверить наличие в текущем заголовке nlmsghdr флага.
- NLMSG_DONE – функция не возвращает значение NULL при завершении обработки сообщения. Второй параметр задает размер оставшейся части буфера сообщения. Макрос уменьшает это значение на размер заголовка сообщения.
- NLMSG_OK – возвращает значение TRUE (1), если сообщение не было усечено и его разборка прошла успешно.
- NLMSG_PAYLOAD – возвращает размер данных (payload), связанных с заголовком nlmsghdr.
От теории к практике
Ну что же. Думаю, что я достаточно помучал Вас теорией 🙂 Может быть что-то показалось запутанным или не понятным – постараюсь разжевать все в наглядных примерах, там на самом деле нет ничего сложного.
Итак, давайте-ка напишем небольшое приложение, которое будет получать уведомления об изменениях в сетевых интерфейсах и таблице маршрутизации! Ниже будет введен целый ряд новых структур и объектов, я о них обязательно расскажу, но обо всем по порядку… Исходный код (монитор интерфейсов):
* monitor.c
* мониторинг сетевых интерфейсов и таблицы маршрутизации
*/
#include <errno.h>
#include <stdio.h>
#include <memory.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <linux/rtnetlink.h>
// небольшая вспомогательная функция, которая с помощью макрасов netlink разбирает сообщение и
// помещает блоки данных в массив атрибутов rtattr
void parseRtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len)
{
memset(tb, 0, sizeof(struct rtattr *) * (max + 1));
while (RTA_OK(rta, len)) { // пока сообщение не закончилось
if (rta–>rta_type <= max) {
tb[rta–>rta_type] = rta; //читаем атрибут
}
rta = RTA_NEXT(rta,len); // получаем следующий атрибут
}
}
int main()
{
int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); // создаем нужный сокет
if (fd < 0) {
printf("Ошибка создания netlink сокета: %s", (char*)strerror(errno));
return 1;
}
struct sockaddr_nl local; // локальный адрес
char buf[8192]; // буфер сообщения
struct iovec iov; // структура сообщения
iov.iov_base = buf; // указываем buf в качестве буфера сообщения для iov
iov.iov_len = sizeof(buf); // указываем размер буфера
memset(&local, 0, sizeof(local)); // очищаем структуру
local.nl_family = AF_NETLINK; // указываем семейство протокола
local.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV4_ROUTE; // указываем необходимые группы
local.nl_pid = getpid(); //в качестве идентификатора указываем идентификатор данного приложения
// структура сообщения netlink – инициализируем все поля
struct msghdr msg;
{
msg.msg_name = &local; // задаем имя – структуру локального адреса
msg.msg_namelen = sizeof(local); // указываем размер
msg.msg_iov = &iov; // указываем вектор данных сообщения
msg.msg_iovlen = 1; // задаем длину вектора
}
if (bind(fd, (struct sockaddr*)&local, sizeof(local)) < 0) { // связываемся с сокетом
printf("Ошибка связывания с netlink сокетом: %s", (char*)strerror(errno));
close(fd);
return 1;
}
// читаем и разбираем сообщения из сокета
while (1) {
ssize_t status = recvmsg(fd, &msg, MSG_DONTWAIT); // после вызова bind() мы можем принимать сообщения для указанных групп
// небольшие проверки
if (status < 0) {
if (errno == EINTR || errno == EAGAIN)
{
usleep(250000);
continue;
}
printf("Ошибка связывания приема сообщения netlink: %s", (char*)strerror(errno));
continue;
}
if (msg.msg_namelen != sizeof(local)) { //провряем длину адреса, мало ли 🙂
printf("Некорректная длина адреса отправителя");
continue;
}
// собственно разбор сообщения
struct nlmsghdr *h; // указатель на заголовок сообщения
for (h = (struct nlmsghdr*)buf; status >= (ssize_t)sizeof(*h); ) { // для всех заголовков сообщений
int len = h–>nlmsg_len; // длина всего блока
int l = len – sizeof(*h); // длина текущего сообщения
char *ifName; // имя соединения
if ((l < 0) || (len > status)) {
printf("Некорректная длина сообщения: %i", len);
continue;
}
// смотрим тип сообщения
if ((h–>nlmsg_type == RTM_NEWROUTE) || (h–>nlmsg_type == RTM_DELROUTE)) { // если это изменения роутов – печатаем сообщение
printf("Произошли изменения в таблице маршрутизации\n");
} else { // в остальных случаях начинаем более детально разбираться
char *ifUpp; // состояние устройства
char *ifRunn; // состояние соединения
struct ifinfomsg *ifi; // указатель на структуру, содержащюю данные о сетевом подключении
struct rtattr *tb[IFLA_MAX + 1]; // массив атрибутов соединения, IFLA_MAX определен в rtnetlink.h
ifi = (struct ifinfomsg*) NLMSG_DATA(h); // получаем информацию о сетевом соединении в кором произошли изменения
parseRtattr(tb, IFLA_MAX, IFLA_RTA(ifi), h–>nlmsg_len); // получаем атрибуты сетевого соединения
if (tb[IFLA_IFNAME]) { // проверяем валидность атрибута, хранящего имя соединения
ifName = (char*)RTA_DATA(tb[IFLA_IFNAME]); //получаем имя соединения
}
if (ifi–>ifi_flags & IFF_UP) { // получаем состояние флага UP для соединения
ifUpp = (char*)"UP";
} else {
ifUpp = (char*)"DOWN";
}
if (ifi–>ifi_flags & IFF_RUNNING) { // получаем состояние флага RUNNING для соединения
ifRunn = (char*)"RUNNING";
} else {
ifRunn = (char*)"NOT RUNNING";
}
char ifAddress[256]; // сетевой адрес интерфейса
struct ifaddrmsg *ifa; // указатель на структуру содержащую данные о сетевом интерфейсе
struct rtattr *tba[IFA_MAX+1]; // массив атрибутов адреса
ifa = (struct ifaddrmsg*)NLMSG_DATA(h); // получаем данные из соединения
parseRtattr(tba, IFA_MAX, IFA_RTA(ifa), h–>nlmsg_len); // получаем атрибуты сетевого соединения
if (tba[IFA_LOCAL]) { // проверяем валидность указателя локального адреса
inet_ntop(AF_INET, RTA_DATA(tba[IFA_LOCAL]), ifAddress, sizeof(ifAddress)); // получаем IP адрес
}
switch (h–>nlmsg_type) { //что конкретно произошло
case RTM_DELADDR:
printf("Был удален адрес интерфейса %s\n", ifName);
break;
case RTM_DELLINK:
printf("Был удален интерфейс %s\n", ifName);
break;
case RTM_NEWLINK:
printf("Новый интерфейс %s, состояние интерфейса %s %s\n", ifName, ifUpp, ifRunn);
break;
case RTM_NEWADDR:
printf("Был добавлен адрес для интерфейса %s: %s\n", ifName, ifAddress);
break;
}
}
status –= NLMSG_ALIGN(len); // выравниваемся по длине сообщения (если этого не сделать – будет очень плохо, можете проверить :))
h = (struct nlmsghdr*)((char*)h + NLMSG_ALIGN(len)); //получаем следующее сообщение
}
usleep(250000); // немножко спим, что бы не очень грузить процессор
}
close(fd); // закрываем дескриптор сокета
return 0;
}Syhi-подсветка кода
Компиляция: gcc monitor.c –o monitor. Запуск: <img1.png>
Пояснения к программе
Как обещал – появились новые структуры. Давайте рассмотрим их, а так же более детально логику приложения:
{
void *iov_base; // буфер данных
__kernel_size_t iov_len; // размер данных
};Syhi-подсветка кода
Эта структура служит хранилищем полезных данных, передаваемых через сокеты netlink. Полю iov_base присваивается указатель на байтовый массив. Именно в этот байтовый массив будут записаны данные сообщения:
void *msg_name; // адрес клиента (имя сокета)
int msg_namelen; // длина адреса
struct iovec *msg_iov; // указатель на блок данных
__kernel_size_t msg_iovlen; // количество блоков данных
void *msg_control; // магическое число для протокола, не используется в данных случаях
__kernel_size_t msg_controllen; // длина предидущего поля данных
unsigned msg_flags; // флаги сообщения
};Syhi-подсветка кода
Эта структура непосредственно передается через сокет. Она содержит в себе указатель на блок полезных данных, количество данных блоков, а так же ряд дополнительных флагов и полей, пришедших, по большей части, с платформы BSD:
{
unsigned char ifi_family; // семейство (AF_UNSPEC)
unsigned short ifi_type; // тип устройства
int ifi_index; // индекс интерфейса
unsigned int ifi_flags; // флаги устройства
unsigned int ifi_change; // маска смены, зарезервировано для использования в будущем и всегда должно быть равно 0xFFFFFFFF
};Syhi-подсветка кода
Эта структура используется для представления сетевого устройства, его семейства, типа, индекса и флагов:
{
unsigned char ifa_family; // Тип адреса (AF_INET или AF_INET6)
unsigned char ifa_prefixlen; // Длина префикса адреса (длина сетевой маски)
unsigned char ifa_flags; // Флаги адреса
unsigned char ifa_scope; // Область адреса
int ifa_index; // Индекс интерфейса, равен аналогичному полю в ifinfomsg
};Syhi-подсветка кода
Эта структура служит для представления сетевого адреса, назначенного на сетевой интерфейс:
{
unsigned short rta_len; // Длина опции
unsigned short rta_type; // Тип опции
/* данные */
}Syhi-подсветка кода
Эта структура* служит для хранения, какого–либо параметра соединения или адреса.
Пояснения к коду
После запуска программы мы создаем netlink сокет и проверяем успешность его создания. Далее происходит объявление необходимых переменных и заполнение структуры локального адреса. Тут мы указываем группы сообщений, на которые хотим подписаться: RTMGRP_LINK, RTMGRP_IPV4_IFADDR, RTMGRP_IPV4_ROUTE.
Так же объявляем структуру сообщения и связываем с ней один блок данных. После этого происходит связывание с сокетом, с помощью bind(). После этого мы становимся подписанными на сообщения для указанных групп. Можно принимать сообщения через сокет.
Далее следует бесконечный цикл приема сообщений из сокета. Так как принимаемый блок данных может иметь несколько заголовков и ассоциированных с ними данных – начинаем перебирать, с помощью netlink макросов все принятые данные. Каждое новое сообщение расположено по указателю struct nlmsghdr *h. Теперь можно разбирать собственно сообщение. Смотрим на поле nlmsg_type и выясняем, что же за сообщение к нам приехало. Если оно связано с таблицей маршрутизации – печатаем сообщение и идем к следующему сообщению. А если нет – начинаем детально разбираться.
Объявляются массивы опций rtattr, куда будут складываться все необходимые данные. За получение этих данных отвечает вспомогательная функция parseRtattr. Она использует макросы Netlink и заполняет указанный массив всеми атрибутами из блока данных структуры ifinfomsg или ifaddrmsg.
После того как мы получили массивы, заполненные атрибутами – можем работать с этим значениями, анализировать их, печатать. Доступ к каждому атрибуту осуществляется по его индексу. Все индексы определены в заголовочных файлах netlink и прокомментированы. В данном случае мы используем следующие индексы:
- IFLA_IFNAME – индекс атрибута с именем интерфейса.
- IFA_LOCAL – индекс атрибута с локальным IP адресом.
После всего этого мы обладаем полной информацией о том, что произошло и можем печатать информацию на экран. Как видите ничего страшного.
Заключение
Итак, мы получили базовые знания о Netlink и его протоколе, рассмотрели все основные структуры и написали наглядный пример. На этом я бы хотел завершить первую часть. Надюсь, что Вам понравилось. Если есть, какие то вопросы – с удовольствием отвечу.
В следующей части мы изучим работу с таблицей маршрутизации и научимся выполнять специализированные запросы. Будет изучено еще ряд структур и методик. Так же будет рассмотрен пример определения своего собственного протокола и общение, с помощью него, со своим модулем в ядре. Для тех, кому нетерпимость изучить что-то новое и кто умеет и хочет учиться чему-то самостоятельно, думаю, пригодится полный RFC протокола Netlink [1], а так же статья в Linux journal [2]. До встречи на страницах журнала клуба ПРОграммистов!
Продолжение следует…
Литература
- RFC-3549 Linux Netlink as an IP Services Protocol http://tools.ietf.org/html/rfc3549
- Kernel Korner — Why and How to Use Netlink Socket http://www.linuxjournal.com/article/7356
Статья из восьмого выпуска журнала «ПРОграммист».
Обсудить на форуме — Работа с NetLink в Linux. Часть 1
Похожие статьи
Купить рекламу на сайте за 1000 руб
пишите сюда - alarforum@yandex.ru
Да и по любым другим вопросам пишите на почту
пеллетные котлы
Пеллетный котел Emtas
Наши форумы по программированию:
- Форум Web программирование (веб)
- Delphi форумы
- Форумы C (Си)
- Форум .NET Frameworks (точка нет фреймворки)
- Форум Java (джава)
- Форум низкоуровневое программирование
- Форум VBA (вба)
- Форум OpenGL
- Форум DirectX
- Форум CAD проектирование
- Форум по операционным системам
- Форум Software (Софт)
- Форум Hardware (Компьютерное железо)